基于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;
});
}