拓展QuerRunner
package jdbc2;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Created by kevin on 2020/4/2.
*/
public class TxQueryRunner extends QueryRunner{
@Override
public int[] batch(String sql, Object[][] params) throws SQLException {
Connection connection = JdbcUtils.getConnection();
int [] result = batch(connection,sql, params);
JdbcUtils.releaseConnection(connection);
return result;
}
@Override
public <T> T query(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
Connection connection = JdbcUtils.getConnection();
T result = query(connection,sql, rsh, params);
JdbcUtils.releaseConnection(connection);
return result;
}
@Override
public <T> T query(String sql, ResultSetHandler<T> rsh) throws SQLException {
Connection connection = JdbcUtils.getConnection();
T result = query(connection,sql, rsh);
JdbcUtils.releaseConnection(connection);
return result;
}
@Override
public int update(String sql) throws SQLException {
Connection connection = JdbcUtils.getConnection();
int result = update(connection,sql);
JdbcUtils.releaseConnection(connection);
return result;
}
@Override
public int update(String sql, Object param) throws SQLException {
Connection connection = JdbcUtils.getConnection();
int result = update(connection,sql,param);
JdbcUtils.releaseConnection(connection);
return result;
}
@Override
public int update(String sql, Object... params) throws SQLException {
Connection connection = JdbcUtils.getConnection();
int result = update(connection,sql,params);
JdbcUtils.releaseConnection(connection);
return result;
}
@Override
public <T> T insert(String sql, ResultSetHandler<T> rsh) throws SQLException {
Connection connection = JdbcUtils.getConnection();
T result = insert(connection,sql, rsh);
JdbcUtils.releaseConnection(connection);
return result;
}
@Override
public <T> T insert(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
Connection connection = JdbcUtils.getConnection();
T result = insert(connection,sql, rsh,params);
JdbcUtils.releaseConnection(connection);
return result;
}
@Override
public <T> T insertBatch(String sql, ResultSetHandler<T> rsh, Object[][] params) throws SQLException {
Connection connection = JdbcUtils.getConnection();
T result = insertBatch(connection,sql, rsh, params);
JdbcUtils.releaseConnection(connection);
return result;
}
}
dao層
package jdbc2;
import org.apache.commons.dbutils.QueryRunner;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Created by kevin on 2020/3/29.
*/
public class AccountDao {
public void update(String name,double money){
try {
QueryRunner queryRunner = new TxQueryRunner();
String sql = "update account set balance = balance+? where name = ?";
Object [] params = {money,name};
//需要保證多次調用的是同一個連接
// Connection connection = JdbcUtils.getConnection();
queryRunner.update(sql,params);
// JdbcUtils.releaseConnection(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
模擬service層
package jdbc2;
import org.junit.Test;
/**
* Created by kevin on 2020/3/30.
*/
public class Demo1 {
private AccountDao dao = new AccountDao();
@Test
public void ServiceMethod(){
try {
//事務連接不會被關閉
JdbcUtils.beginTransaction();
dao.update("zs",10000);
if(true)throw new RuntimeException("出異常了,給我回滾!");//事務測試
dao.update("ls",-10000);
JdbcUtils.commitTransaction();
} catch (Exception e) {
try {
JdbcUtils.rollbackTransaction();
} catch (Exception e1) {
e1.printStackTrace();
}
throw e;
}
}
}
jdbcutils
要點
jdbcutils處理多線程併發問題描述:
1如果開始事務同時被2個線程開啓,第二個將會拋出異常–已開啓事務
2如果第一個線程開啓後第二線程進行提交,第一個提交時會拋出異常——沒有事務
原因:共享了同一個connection
解決:使用ThreadLocal
package jdbc2;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 實現功能
* 提供連接池獲取連接
* 提供事務連接
* 提供開關事務
* 提供釋放非事務連接
* 支持多線程併發訪問
* Created by kevin on 2020/3/29.
*/
public class JdbcUtils {
//加載配置文件的默認配置,需要配置c3p0-config.xml(放在src/main/Resource目錄下即可)
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
//事務專用連接
// private static Connection connection = null;
//由原來的一個對象成爲了一個存儲對象的容器
private static ThreadLocal<Connection> connectionThreadLocal = new InheritableThreadLocal<Connection>();
/**
* 如果connection不爲null那麼返回的就是事務專用連接
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException{
Connection connection = connectionThreadLocal.get();
if(connection!=null){
return connection;
}else{
return dataSource.getConnection();
}
}
public static DataSource getDataSource(){
return dataSource;
}
/**
* 開啓事務
* 1.獲取一個Connection,設置它的setAutoCommit(false)
* 2.還要保證dao中使用的連接是我們剛剛創建的
*/
public static void beginTransaction(){
Connection connection = connectionThreadLocal.get();//獲取當前線程專用連接
try {
if(connection!=null){
throw new SQLException("已經開啓事務,請勿重複操作");
}
connection = getConnection();
connection.setAutoCommit(false);
connectionThreadLocal.set(connection);//存儲當前線程專用連接
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 提交事務
* 1.獲取beginTransaction提供的Connection,然後調用commit方法
*/
public static void commitTransaction(){
Connection connection = connectionThreadLocal.get();//獲取當前線程專用連接
try {
if(connection==null){
throw new SQLException("還沒有開啓事務!不能提交");
}
connection.commit();
connection.close();
connectionThreadLocal.remove();//移除當前線程專用連接
// connection = null;//必須清空值否則下次會得到一個已經關閉的連接
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 回滾事務
* 1.獲取beginTransaction提供的Connection,然後調用了rollback方法
*/
public static void rollbackTransaction(){
Connection connection = connectionThreadLocal.get();//獲取當前線程專用連接
try {
if(connection==null){
throw new SQLException("還沒有開啓事務!不能回滾");
}
connection.rollback();
connection.close();
connectionThreadLocal.remove();//移除當前線程專用連接
// connection= null;//必須清空值否則下次會得到一個已經關閉的連接
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 釋放非事務專用連接
* 判斷它是不是事務專用連接,如果是,不關閉
* 如果不是,就進行關閉
*
*/
public static void releaseConnection(Connection inCon) throws SQLException{
Connection connection = connectionThreadLocal.get();//獲取當前線程專用連接
//如果connection == null 說明現在沒有事務,那麼connection一定不是事務專用
if(connection == null){
inCon.close();
}
//如果connection!=null 說明有事務,那麼需要判斷連接參數是否與connection相等
//若不等說明參數連接不是事務專用連接
if(connection!=inCon){
inCon.close();
}
}
}