前提
最近在某个公司实习,框架使用的是springmvc+spring+mybatis,具体名字就不说了,所有事务都没有用无法回滚,导师叫我和db人员对接,我就写了一个Jdbc的原生的测试测试代码如下:
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//url写测试中间件的,这里我替换了
String url="jdbc:mysql://localhost:3306/taotao?characterEncoding=utf-8";
String username="root";
String passwd="123456";
Connection conn= null;
try {
conn = DriverManager.getConnection(url, username, passwd);
} catch (SQLException e) {
e.printStackTrace();
}
try{
conn.setAutoCommit(false);
PreparedStatement pstmt = conn.prepareStatement("insert into lztt (text) VALUES (?)") ;
pstmt.execute("start TRANSACTION ");
pstmt.setString(1,"memeda");
pstmt.executeUpdate();
PreparedStatement pstmt1 = conn.prepareStatement("insert into lztt (text) VALUES (?)") ;
pstmt1.setString(1,"dsajdijsaoidjiosajdoisajdoijsaoidjsaoijdoisajidjsoajdijsaidjsajdsad");
pstmt1.executeUpdate();
conn.commit();
}catch (Exception e){
try {
System.out.println(1);
conn.rollback();
System.out.println(2);
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
try {
conn.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
}
通过上面的代码发现,是setautocommit(flase)不起作用,不起作用咋办,难道事务就这样没办法回滚了吗,这里我们可以手动的使用pstmt来开启事务这样就可以了pstmt.execute(“start TRANSACTION “);这是我们原生的使用方法,但是非原生的使用怎么办,也就是我们使用框架mybatis和spring。接下来介绍我的思路:
思路一
修改Spring的事务管理器,看过Spring源码的朋友都知道DataSourceTransactionManager是我们管理Spring和Mybatis事务的地方,doBegin()方法就是我们创建事务的地方,doBegin方法代码如下:
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
if (txObject.getConnectionHolder() == null ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = this.dataSource.getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// Bind the session holder to the thread.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
DataSourceUtils.releaseConnection(con, this.dataSource);
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
这里有一句话:con.setAutoCommit(false);但是对我们的中间件不起作用,这时我的思路来了,继承这个类然后重写doBegin()方法,然而我发现这是不可能的,有些对象的方法是protected我子类根本没办法使用,这里我放弃了。
思路二
所有代码重写,这工作量太大,考虑了一会就没想了
思路三
这也是我后面采取的思路,利用自定义注解加AOP的方法实现。我们可以利用AOP在方法执行之前就可以执行数据库的东西,这下问题来了,我怎么才能获得数据库连接了,看spring源码找了好久,找到了一个工具,利用数据库连接池可以获得绑定我们线程的链接。Connection connection = DataSourceUtils.getConnection(this.dataSource);
具体的切面:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Created by lz on 2016/7/11.
*/
@Component
@Aspect
public class TransactionBeforeAop {
private static final Logger log = LoggerFactory.getLogger(TransactionBeforeAop.class);
@Autowired
private DataSource dataSource;
@Before("@annotation(com.bank.TransactionBefore)")
public void before(JoinPoint joinPoint){
Connection connection = DataSourceUtils.getConnection(this.dataSource);
try {
connection.prepareStatement("").execute("start TRANSACTION");
} catch (SQLException e) {
log.error("occur Exception",e.getMessage());
}
}
}
注解定义如下:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by lz on 2016/7/11.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface TransactionBefore {
}
完成后我们就可以在Service中使用了。