基于Jfinal3.4(老版本也能如此操作,但是需要多一步就是了)
SpringBoot
// ========= SpringBoot ============ @Configuration public class JDbConfig { @Autowired // 等待db1Source启动后注入 @Qualifier("db1Source") private DataSource ds; /** * 注入 * @return JDb (JDb enxtends Db) * @throws Exception */ @Bean(name = "jdb") @Primary public JDb jdb() throws Exception { ActiveRecordPlugin arp = new ActiveRecordPlugin(ds); arp.getEngine().setSourceFactory(new ClassPathSourceFactory()); arp.addSqlTemplate("/sql/all_sqls.sql"); // 启动Record容器 arp.start(); System.out.println("===Jfinal - Db启动成功==="); // Db初始化 JDb db = new JDb(); return db; } }
// ===== JDb.java ============ /** * 集成jfinal Db * @author MrYang * */ public class JDb extends Db{ }
springBoot到这里的配置就结束了,就是这么简单!!!
下面的部分是 SpringMvc 的部分
========= SpringMvc============
在xml里配置个项目启动类,然后将数据源赋予这个类即可
例如:
<!-- CoralDbc -->
<bean id="jdb" class="cn.com.???.coral.core.db.JDb" init-method="start">
<property name="datasource">
<ref bean="datasource" />
</property>
<property name="sqlPath" value="/sql/all_sqls.sql" />
</bean>
/** * 集成jfinal Db * @author MrYang * */ public class JDb extends Db{ /** * 使用spring的jdbc对象,不然不知道为什么使用引入的 DataSource ds 对象,有时候是无效的 */ @Autowired JdbcTemplate jdbcTemplate; private String sqlPath; public void start(){ // 加载springJdbc一致的数据源 ActiveRecordPlugin arp = new ActiveRecordPlugin(jdbcTemplate.getDataSource()); arp.getEngine().setSourceFactory(new ClassPathSourceFactory()); // arp.addSqlTemplate(sqlPath); // 启动Record容器 arp.start(); System.out.println("===Jfinal - Db启动成功==="); } public String getSqlPath(){ return this.sqlPath; } public void getSqlPath(String sqlPath){ this.sqlPath=sqlPath; } }
=============================================
如果是直接用 jfinal 事务去操作的,而不需要用到spring事务注解标签的,请忽略这部分内容
jfinal事务和spring事务,如何统一整合,直接使用spring的事务标签呢,这个需要单独建一个插件或者直接复写 com.jfinal.plugin.activerecord.Config.getConnection() 和getThreadLocalConnection() 方法
或者切面在 service 层实现(然后要求大家遵守约束规范)
Config.java
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.jfinal.plugin.activerecord; import com.jfinal.kit.LogKit; import com.jfinal.kit.StrKit; import com.jfinal.plugin.activerecord.cache.EhCache; import com.jfinal.plugin.activerecord.cache.ICache; import com.jfinal.plugin.activerecord.dialect.Dialect; import com.jfinal.plugin.activerecord.dialect.MysqlDialect; import com.jfinal.plugin.activerecord.sql.SqlKit; import org.springframework.jdbc.datasource.ConnectionHolder; import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.transaction.support.TransactionSynchronizationManager; import javax.sql.DataSource; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Map; /** * @ClassName Config * @Description: 基于spring事务管理改写,兼容 @Transactional 与 Db.use().tx(......) 类型事务混写 * @Author MrYang * @Date 2020/10/13 * @Version V1.0 **/ public class Config { // 如果这里是静态的话,会导致线程公用同一个连接对象 private final ThreadLocal<Connection> threadLocal ; String name; DataSource dataSource; Dialect dialect; boolean showSql; boolean devMode; int transactionLevel; IContainerFactory containerFactory; IDbProFactory dbProFactory; ICache cache; SqlKit sqlKit; Config(String name, DataSource dataSource, int transactionLevel) { this.threadLocal = new ThreadLocal(); this.dbProFactory = IDbProFactory.defaultDbProFactory; this.init(name, dataSource, new MysqlDialect(), false, false, transactionLevel, IContainerFactory.defaultContainerFactory, new EhCache()); } public Config(String name, DataSource dataSource, Dialect dialect, boolean showSql, boolean devMode, int transactionLevel, IContainerFactory containerFactory, ICache cache) { this.threadLocal = new ThreadLocal(); this.dbProFactory = IDbProFactory.defaultDbProFactory; if (dataSource == null) { throw new IllegalArgumentException("DataSource can not be null"); } else { this.init(name, dataSource, dialect, showSql, devMode, transactionLevel, containerFactory, cache); } } private void init(String name, DataSource dataSource, Dialect dialect, boolean showSql, boolean devMode, int transactionLevel, IContainerFactory containerFactory, ICache cache) { if (StrKit.isBlank(name)) { throw new IllegalArgumentException("Config name can not be blank"); } else if (dialect == null) { throw new IllegalArgumentException("Dialect can not be null"); } else if (containerFactory == null) { throw new IllegalArgumentException("ContainerFactory can not be null"); } else if (cache == null) { throw new IllegalArgumentException("Cache can not be null"); } else { this.name = name.trim(); this.dataSource = dataSource; this.dialect = dialect; this.showSql = showSql; this.devMode = devMode; this.setTransactionLevel(transactionLevel); this.containerFactory = containerFactory; this.cache = cache; this.sqlKit = new SqlKit(this.name, this.devMode); } } public Config(String name, DataSource dataSource) { this(name, dataSource, new MysqlDialect()); } public Config(String name, DataSource dataSource, Dialect dialect) { this(name, dataSource, dialect, false, false, 4, IContainerFactory.defaultContainerFactory, new EhCache()); } private Config() { this.threadLocal = new ThreadLocal(); this.dbProFactory = IDbProFactory.defaultDbProFactory; } void setDevMode(boolean devMode) { this.devMode = devMode; this.sqlKit.setDevMode(devMode); } void setTransactionLevel(int transactionLevel) { if (transactionLevel != 0 && transactionLevel != 1 && transactionLevel != 2 && transactionLevel != 4 && transactionLevel != 8) { throw new IllegalArgumentException("The transactionLevel only be 0, 1, 2, 4, 8"); } else { this.transactionLevel = transactionLevel; } } static Config createBrokenConfig() { Config ret = new Config(); ret.dialect = new MysqlDialect(); ret.showSql = false; ret.devMode = false; ret.transactionLevel = 4; ret.containerFactory = IContainerFactory.defaultContainerFactory; ret.cache = new EhCache(); return ret; } public String getName() { return this.name; } public SqlKit getSqlKit() { return this.sqlKit; } public Dialect getDialect() { return this.dialect; } public ICache getCache() { return this.cache; } public int getTransactionLevel() { return this.transactionLevel; } public DataSource getDataSource() { return this.dataSource; } public IContainerFactory getContainerFactory() { return this.containerFactory; } public IDbProFactory getDbProFactory() { return this.dbProFactory; } public boolean isShowSql() { return this.showSql; } public boolean isDevMode() { return this.devMode; } /** * 设置当前线程连接 * @param connection */ public void setThreadLocalConnection(Connection connection) { this.threadLocal.set(connection); //System.out.println("当前线程2-1:"+Thread.currentThread().getId()+ "对象:" + this.threadLocal.get()); } /** * 移除当前线程连接 */ public void removeThreadLocalConnection() { //System.out.println("当前线程2-3:"+Thread.currentThread().getId() + "准备释放对象:" + this.threadLocal.get()); // 移除事务层 this.threadLocal.remove(); } /** * 获取连接 * @return * @throws SQLException */ public Connection getConnection() throws SQLException { //System.out.println("当前线程2-2:"+Thread.currentThread().getId() + "对象:" + this.threadLocal.get()); Connection conn = null; ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); // 不是spring的事务管理,则走原来的逻辑 if(conHolder == null ){ conn = (Connection)this.threadLocal.get(); if (conn != null) { return conn; } else { return this.showSql ? (new SqlReporter(this.dataSource.getConnection())).getConnection() : this.dataSource.getConnection(); } }else{ // conn = (Connection)this.threadLocal.get(); if (conn != null) { return conn; } else { // 通过spring获取连接对象 conn = DataSourceUtils.getConnection(this.getDataSource()); } } return conn; } /** * 获取当前连接 -- 如果已经有事务的话,这里是会有对象的 * @return */ public Connection getThreadLocalConnection() { Connection connection = threadLocal.get(); if(connection != null){ return connection; } ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); if(conHolder == null){ return threadLocal.get(); }else{ return DataSourceUtils.getConnection(this.getDataSource()); } } /** * 是否在事务中 * @return */ public boolean isInTransaction() { ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); // 不是spring的事务管理,则走原来的逻辑 if(conHolder == null){ return threadLocal.get() != null; } return conHolder != null && (conHolder.getConnectionHandle()!=null || conHolder.isSynchronizedWithTransaction()); } public void close(ResultSet rs, Statement st, Connection conn) { if (rs != null) { try { rs.close(); } catch (SQLException var7) { LogKit.error(var7.getMessage(), var7); } } if (st != null) { try { st.close(); } catch (SQLException var6) { LogKit.error(var6.getMessage(), var6); } } // 关闭连接 close(conn); } public void close(Statement st, Connection conn) { if (st != null) { try { st.close(); } catch (SQLException var5) { LogKit.error(var5.getMessage(), var5); } } // 关闭连接 close(conn); } /** * 改造关闭对象 * @param conn */ public void close(Connection conn) { // 是否在事务中 ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); // spring事务则走这个方法 && !conHolder.isSynchronizedWithTransaction() if (conHolder != null ) { // 直接交给spring来关闭 -- 是否释放,在spring里会有判断 DataSourceUtils.releaseConnection(conn, dataSource); }else{ // 原 jfinal 事务走法 if (threadLocal.get() == null){ // 连接 if (conn != null) { try { //conn.close(); DataSourceUtils.releaseConnection(conn, dataSource); } catch (Throwable e) { e.printStackTrace(); } } } } } }
这样子,大家就可以愉快的用spring的事务玩耍了!!!!
备注:2020-11-02 修复之前线程池一直占用没有恢复连接的bug(备注:还有一个DbPro对象改造后,才能进行多数据源的切换和联动),如果要让异步事务 txInNewThread 的事务生效,需要改写对应的 tx 方法,便于扩展兼容,不扩展的话,只有当前线程事务可以交互拿到对应的数据
==============================================
项目里使用的时候,直接注入即可
@Autowired JDb jdb; public void test(){ // 接下来,你们懂的 jdb.find("select count(1) from tb"); // 读取sql脚本 jdb.getSqlPara("index.getProjectList",100) }
对应的日志:
// jdb.find("select count(1) from tb"); 2018-07-05 17:41:12.831|XNIO-2 task-1|DEBUG|{conn-10005, pstmt-20000} created. select count(1) from tb // jdb.getSqlPara("index.getProjectList",100) Sql: select p.id, substring(p.title, 1, 100) as title, substring(p.content, 1, 180) as content, a.avatar, a.id as accountId from project p inner join account a on p.accountId = a.id where report < ? order by p.id asc limit 10 Para: [100]
事务一致性的一个小测试:
User user = userService.findByUsername("xxx"); user.setNickname("测试名称1"// 执行保存 -- 必须是带有flush的保存,否则只会存在jpa的缓存层,导致同一个事务层的jdbc查询的时候查找不到 userService.update(user); // Record record = jdb.findFirst("select * from sys_user where user_name='xxx'"); // 打印的是 测试名称1 System.out.println("jfinal查询:"+record.getStr("nick_name")); // 打印的是 测试名称1 System.out.println("spring自带查询:"+jdbcTemplate.queryForMap("select * from sys_user where user_name='xxx'").get("nick_name"));
特殊案例应用,非事务和事务的结合 -- 2020-08-07 -- 兼容处理修正后方可实现:
/** * 在普通方法里独立开启事务,与基础操作区分开来 */ public void test2(){ Organization organization=new Organization(); organization.setOrgCode("JG001"); organization.setOrgName("机构001"); organization.setPid(0L); organizationService.save(organization); // 设置为null,准备在插入一个新的对象,然后设置事务回滚 return false ,结果是不会插入到数据库 organization.setOrgId(null); Db.use().tx(Connection.TRANSACTION_SERIALIZABLE, () -> { // save 其实默认将主键给剔除了 JDb.save(organization); return false; }); }