SpringBoot or Spring 接入 Jfinal-Db 替换Mybatis和datasource资源池共享

基于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.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;

/**
 * 基于spring事务管理改写
 * 只适合spring的 @Transactional 注解方法
 */
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);
    }

    /**
     * 移除当前线程连接
     */
    public void removeThreadLocalConnection() {
        this.threadLocal.remove();
    }


    /**
     * 获取连接
     * @return
     * @throws SQLException
     */
    public Connection getConnection() throws SQLException {
        // 获取连接改造成spring的连接对象
        Connection conn = DataSourceUtils.getConnection(this.getDataSource());
//        // 设置当前线程连接对象
        this.setThreadLocalConnection(conn);
        return  conn ;
    }

    /**
     * 获取当前连接
     * @return
     */
    public Connection getThreadLocalConnection() {
        try {
            return getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 是否在事务中
     * @return
     */
    public boolean isInTransaction() {
        return TransactionSynchronizationManager.isSynchronizationActive();
    }

    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) {
    
                // 判断this.threadLocal.get()主要是因为需要在没有线程执行的时候去做处理,如果有的话,说明连接还不能去主动释放
                if(this.threadLocal.get() == null && conn != null) {
                    try {
                        DataSourceUtils.releaseConnection(conn, dataSource);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }

        // 判断this.threadLocal.get()主要是因为需要在没有线程执行的时候去做处理,如果有的话,说明连接还不能去主动释放
//        if(this.threadLocal.get() == null && conn != null){
//            ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
//            //判断当前是否为事务环境
//            if(conHolder == null || !conHolder.isSynchronizedWithTransaction()){
//                try {
//                    //非事务环境交由spring释放conn
//                    DataSourceUtils.releaseConnection(conn,dataSource);
//                    System.out.println("释放连接");
//                } catch (Exception e) {
//                    e.printStackTrace();
//                }
//            }
//        }


    }



}


这样子,大家就可以愉快的用spring的事务玩耍了!!!! 

备注:两个事务事件请不要混用!!! 或者要改造的点就更多了!!!

==============================================


项目里使用的时候,直接注入即可

    @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"));



评论区

小佳

2018-07-05 16:40

因为jfinal在某方面比spring强大多了,包括sql模板的部分,所以用了以上方法,就可以把 Mybatis 踢掉,实现高效的开发

JFinal

2018-07-05 16:52

要是再来一点使用的代码就好了,感谢你的分享

小佳

2018-07-05 17:44

peefau

2018-07-05 18:05

学习一下

晴天009

2018-08-23 17:50

jfinal事务和spring事务,如何统一整合 楼主能再说详细点吗 谢谢

晴天009

2018-08-23 17:53

jdb都是静态方法 注入好像意义不大

小佳

2018-09-08 14:29

@晴天009 嗯,习惯性用法,因为我秉承spirng体系一脉,会比较舒服点,让spring-bean托管容器,是根本性意识

小佳

2018-09-08 14:43

@晴天009 我文章有小改,关于tx事务和jfinal事务重叠的部分,需要替换对应的类和方法,建议做成中间插件覆盖即可

WenJ

2019-09-12 10:45

感谢楼主分享,在实际整合到spring过程中,Config.close(),关闭链接的方法也需要处理,否则当存在事务操作的业务中,第二次操作数据库则会报错,事务操作关闭conn操作应该交给spring自行处理。而且我在查看druid检测页面时发现,逻辑打开连接数与逻辑关闭连接数在druid 1.0.29版本会出现不一致的情况,可能是druid自身统计bug造成,升级至1.1.18后问题解决。我的close代码修改如下:
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
//判断当前是否为事务环境
if(conHolder == null || !conHolder.isSynchronizedWithTransaction()){
try {
//非事务环境释放conn
DataSourceUtils.doReleaseConnection(conn,dataSource);
} catch (SQLException e) {
e.printStackTrace();
}
}

小佳

2019-11-18 16:50

@WenJ 非常感谢,其实我这边也是有做了调整,基本大致流程与你所述一致,看你写的代码和注释,也是老鸟一枚啊

小佳

2019-11-18 17:42

@WenJ 我这里其实是用切面实现,然后在after那里,还会做一次处理,就是每次结束后,统一将就final的connect对象置为null,呢么处理connect的释放,就还是交由spring自己处理,效果与在jfinal的close那里的处理是一致的