Solon整合hibernate、Solon使用hibernate
Solon整合hibernate、Solon使用hibernate
环境
jdk17+ ,solon 2.5.4(当前最新:2023年9月13日09:42:12)、hibernate 6.3(当前最新版本:2023年9月13日09:42:31)
依赖
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-web</artifactId>
<version>2.5.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-core -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.orm/hibernate-core -->
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.3.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.1.0</version>
</dependency>
定义一个hibernate配置
import cn.hutool.core.util.ClassUtil;
import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.context.internal.ThreadLocalSessionContext;
import javax.sql.DataSource;
import java.lang.reflect.Proxy;
import java.util.Properties;
import java.util.Set;
/**
* @author lingkang
* created by 2023/9/11
*/
public class HibernateConfiguration extends Configuration {
public HibernateConfiguration() {
}
/**
* 添加实体包扫描,有hibernate的@Table、@Entity
*/
public HibernateConfiguration addScanPackage(String... packagePath) {
if (packagePath != null)
for (String pack : packagePath) {
Set<Class<?>> classes = ClassUtil.scanPackage(pack);
for (Class<?> clazz : classes)
addAnnotatedClass(clazz);
}
return this;
}
public HibernateConfiguration setDataSource(DataSource dataSource) {
if (dataSource != null) {
this.getProperties().put(AvailableSettings.DATASOURCE, dataSource);
}
return this;
}
public HibernateConfiguration setProperties(Properties properties) {
if (properties != null) {
properties.entrySet().forEach(obj -> {
getProperties().put(obj.getKey(), obj.getValue());
});
}
return this;
}
@Override
public SessionFactory buildSessionFactory() throws HibernateException {
getProperties().put(AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, "jdbc");
getProperties().put(AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS, ThreadLocalSessionContext.class.getName());
SessionFactory sessionFactory = super.buildSessionFactory();
return sessionFactory;
}
}
一定要开启当前线程回话上下文,因为没有实现JTA事务
配置
import com.zaxxer.hikari.HikariDataSource;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AvailableSettings;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.annotation.Inject;
import top.lingkang.ciyuanwaf.config.h.HibernateConfiguration;
import javax.sql.DataSource;
import java.util.Properties;
/**
* @author lingkang
* Created by 2023/9/9
*/
@Configuration
public class DbConfig {
@Bean
public DataSource db1(@Inject("${db1}") HikariDataSource ds) {
ds.setPoolName("db1");
ds.setMaximumPoolSize(10);
return ds;
}
@Bean
public SessionFactory sessionFactory(@Inject DataSource dataSource) {
Properties properties = new Properties();
// properties.put(AvailableSettings.DIALECT, MySQLDialect.class.getName());
// properties.put(AvailableSettings.JAKARTA_JTA_DATASOURCE, dataSource);
properties.put(AvailableSettings.DATASOURCE, dataSource);
/*properties.put(AvailableSettings.DRIVER,"com.mysql.cj.jdbc.Driver");
properties.put(AvailableSettings.URL,"jdbc:mysql://localhost:3306/cy?useUnicode=true&nullCatalogMeansCurrent=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai");
properties.put(AvailableSettings.USER,"root");
properties.put(AvailableSettings.PASS,"123456");*/
HibernateConfiguration configuration = new HibernateConfiguration();
configuration.addScanPackage("top.lingkang.ciyuanwaf.entity");
configuration.setProperties(properties);
return configuration.buildSessionFactory();
}
}
app.yml
配置
db1:
jdbcUrl: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=true
driverClassName: com.mysql.cj.jdbc.Driver
username: root
password: 123456
配置事务
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.noear.solon.Solon;
import org.noear.solon.core.aspect.Invocation;
import org.noear.solon.data.around.TranInterceptor;
/**
* @author lingkang
* created by 2023/9/12
*/
public class HibernateTranInterceptor extends TranInterceptor {
private SessionFactory sessionFactory;
private static final ThreadLocal<Integer> tranNumber = new ThreadLocal<>();
@Override
public Object doIntercept(Invocation inv) throws Throwable {
if (sessionFactory == null)
sessionFactory = Solon.context().getBean(SessionFactory.class);
Session session = sessionFactory.getCurrentSession();
if (tranNumber.get() == null)
tranNumber.set(0);
else
tranNumber.set(tranNumber.get() + 1);
Object result = null;
try {
if (!session.getTransaction().isActive())
session.beginTransaction();
result = super.doIntercept(inv);
if (tranNumber.get() == 0) {
session.getTransaction().commit();
} else tranNumber.set(tranNumber.get() - 1);
} catch (Throwable e) {
session.getTransaction().rollback();
throw e;
}
return result;
}
}
注意:事务提交/回滚后,会自动关闭当前回话的
插件类
import org.noear.solon.core.AppContext;
import org.noear.solon.core.Plugin;
import org.noear.solon.data.annotation.Tran;
import top.lingkang.ciyuanwaf.config.h.a.DaoMapperBeanBuilder;
import top.lingkang.ciyuanwaf.config.h.a.Mapper;
/**
* @author lingkang
* created by 2023/9/11
*/
public class HibernatePlugin implements Plugin {
@Override
public void start(AppContext context) throws Throwable {
context.beanInterceptorAdd(Tran.class, new HibernateTranInterceptor(), 1);
}
@Override
public void prestop() throws Throwable {
Plugin.super.prestop();
}
@Override
public void stop() throws Throwable {
Plugin.super.stop();
}
}
插件top.lingkang.ciyuanwaf.config.properties
文件如下
src/main/resources/META-INF/solon/top.lingkang.ciyuanwaf.config.properties
内容如下:
solon.plugin=top.lingkang.ciyuanwaf.config.h.HibernatePlugin
实体类
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.PrePersist;
import lombok.Data;
import java.util.Date;
/**
* @author lingkang
* Created by 2023/9/9
*/
@Data
@MappedSuperclass
public class BaseTime {
@Column(name = "createTime")
private Date createTime;
@Column(name = "updateTime")
private Date updateTime;
@PrePersist// 新增时自动修改时间
public void prePersist() {
if (createTime == null)
createTime = new Date();
if (updateTime == null)
updateTime = createTime;
}
}
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;
import java.util.Date;
/**
* @author lingkang
* created by 2023/9/8
*/
@Data
@Entity
@Table(name = "http_entity")
public class HttpEntity extends BaseTime{
@Id
private String id;
@Column(name = "description")
private String description;
@Column(name = "type")
private String type;
}
调用
注意,solon未适配JTA事务处理,所有操作均在事务中执行(查询也要在事务中执行,hibernate底层即使是查询也会检查事务是否开启)
package top.lingkang.ciyuanwaf.controller;
import com.alibaba.fastjson2.JSONObject;
import com.zaxxer.hikari.HikariDataSource;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Inject;
import org.noear.solon.annotation.Mapping;
import org.noear.solon.core.handle.Context;
import org.noear.solon.data.annotation.Tran;
import top.lingkang.ciyuanwaf.dao.HttpEntityDao;
import top.lingkang.ciyuanwaf.entity.HttpEntity;
import java.util.List;
/**
* @author lingkang
* created by 2023/9/8
*/
@Controller
public class WebController {
@Inject
private SessionFactory sessionFactory;
private Session getSession() {
return sessionFactory.getCurrentSession();
}
@Tran// 任何数据库操作都要开启事务
@Mapping("/t")
public void t1() {
HttpEntity entity = new HttpEntity();
entity.setId(System.currentTimeMillis() + "");
httpEntityDao.saveOrUpdate(entity);
System.out.println(JSONObject.toJSONString(entity));
/*int update = getSession().createQuery(
"update HttpEntity set updateTime=?1 where id='current'"
).setParameter(1, new Date()).executeUpdate();
System.out.println(update);*/
}
@Tran// 任何数据库操作都要开启事务
@Mapping("/t2")
public Object t2() {
HttpEntity entity = new HttpEntity();
entity.setId(System.currentTimeMillis() + "");
httpEntityDao.saveOrUpdate(entity);
List list = getSession()
.createQuery("select e from HttpEntity e where e.id=?1")
.setParameter(1,entity.getId())
.setMaxResults(1).list();
return list;
}
@Tran// 任何数据库操作都要开启事务
@Mapping("/t3")
public Object t3() {
List list = getSession()
.createQuery("select e from HttpEntity e")
.setMaxResults(1).list();
return list;
}
}
实现JPA的JpaRepository效果
JPA可以通过继承JpaRepository
接口来快速save
、findAll
等操作,底层原理是使用代理这个接口实现。
1、定义@Mapper,对标jpa的@Repository注解、定义Dao,对标实现JPA的JpaRepository接口
import java.lang.annotation.*;
/**
* @author lingkang
* created by 2023/9/12
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Mapper {
}
import java.io.Serializable;
/**
* @author lingkang
* created by 2023/9/12
*/
public interface Dao<E> {
void saveOrUpdate(E entity);
void deleteById(Serializable id);
}
2、定义@Mapper接口代理处理
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.noear.solon.Solon;
import top.lingkang.ciyuanwaf.config.h.a.Dao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author lingkang
* created by 2023/9/12
*/
public class HibernateDaoInvocationHandler implements InvocationHandler {
private SessionFactory sessionFactory;
private Class<?> entityClass;
private Class<?> daoInterface;
public HibernateDaoInvocationHandler(Class<?> entityClass, Class<?> daoInterface) {
this.entityClass = entityClass;
this.daoInterface = daoInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Dao.class) {
if ("saveOrUpdate".equals(method.getName())) {
JpaUtils.checkNotNull(args, "保存对象不能为空");
JpaUtils.checkNotNull(args[0], "保存对象不能为空");
Session session = session();
session.persist(args[0]);// 保存或更新
}
}
return null;
}
private Session session() {
if (sessionFactory == null)
sessionFactory = Solon.context().getBean(SessionFactory.class);
return sessionFactory.getCurrentSession();
}
}
上面我只做了一个saveOrUpdate做例子,还有findAll、根据字段find也要自己解析,也没有做类的检查等,可自行实现
3、编写solon的BeanBuilder应用我们的接口代理
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.core.BeanBuilder;
import org.noear.solon.core.BeanWrap;
import top.lingkang.ciyuanwaf.config.h.HibernateDaoInvocationHandler;
import top.lingkang.ciyuanwaf.config.h.JpaUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
/**
* @author lingkang
* created by 2023/9/12
*/
@Slf4j
public class DaoMapperBeanBuilder implements BeanBuilder<Mapper> {
@Override
public void doBuild(Class<?> clz, BeanWrap bw, Mapper anno) throws Throwable {
if (clz.isInterface()) {
Type[] interfaces = clz.getGenericInterfaces();
Type type = interfaces[0];
String className = getEntityClassName(type.getTypeName());
Class<?> forName = Class.forName(className);
// log.info(getEntityClassName(type.getTypeName()));
Object proxyInstance = Proxy.newProxyInstance(
this.getClass().getClassLoader(),
new Class[]{clz}, new HibernateDaoInvocationHandler(forName, clz)
);
Field field = bw.getClass().getDeclaredField("raw");
field.setAccessible(true);
field.set(bw, proxyInstance);
} else
throw new IllegalArgumentException("@Mapper 只能作用在接口上,当前:" + clz.getName());
}
// top.lingkang.ciyuanwaf.config.Dao<top.lingkang.ciyuanwaf.entity.ItemEntity>
// 将返回实体类:top.lingkang.ciyuanwaf.entity.ItemEntity
private String getEntityClassName(String name) {
String substring = name.substring(38);
return substring.substring(0, substring.length() - 1);
}
}
将DaoMapperBeanBuilder
配置到solon,修改之前的HibernatePlugin
加入下面的内容
context.beanBuilderAdd(Mapper.class, new DaoMapperBeanBuilder());
import org.noear.solon.core.AppContext;
import org.noear.solon.core.Plugin;
import org.noear.solon.data.annotation.Tran;
import top.lingkang.ciyuanwaf.config.h.a.DaoMapperBeanBuilder;
import top.lingkang.ciyuanwaf.config.h.a.Mapper;
/**
* @author lingkang
* created by 2023/9/11
*/
public class HibernatePlugin implements Plugin {
@Override
public void start(AppContext context) throws Throwable {
context.beanBuilderAdd(Mapper.class, new DaoMapperBeanBuilder());
context.beanInterceptorAdd(Tran.class, new HibernateTranInterceptor(), 1);
}
@Override
public void prestop() throws Throwable {
Plugin.super.prestop();
}
@Override
public void stop() throws Throwable {
Plugin.super.stop();
}
}
4、编写dao(mapper)
import top.lingkang.ciyuanwaf.config.h.a.Mapper;
import top.lingkang.ciyuanwaf.config.h.a.Dao;
import top.lingkang.ciyuanwaf.entity.HttpEntity;
/**
* @author lingkang
* created by 2023/9/12
*/
@Mapper
public interface HttpEntityDao extends Dao<HttpEntity> {
}
5、调用Dao
@Inject
HttpEntityDao httpEntityDao;
@Tran// 任何数据库操作都要开启事务
@Mapping("/t")
public void t1() {
HttpEntity entity = new HttpEntity();
entity.setId(System.currentTimeMillis() + "");
httpEntityDao.saveOrUpdate(entity);
System.out.println(JSONObject.toJSONString(entity));
}