Native jdbc transaction management is relatively complicated, requiring manual commit and rollback, but also a bunch of try-catch. The students know the familiar spring, spring uses a declarative approach to transaction management services, the transaction management change very simple. Spring transaction is very strong, I only used here to simulate a few simple jdbc property.
1. declarative transaction design
Declarative transaction based primarily on java dynamic proxy implementation
By Connection stored in a ThreadLocal variable to solve concurrency issues. Spring is also the underlying use ThreadLocal.
By recording Connection creator, to solve the problem of nested transactions.
Custom annotation @EnableTranscation: the method used to indicate whether the transaction open
Service Factory: create a container used to simulate the Spring Bean course, if you are using a modified method @EnableTranscation Service included, Service proxy object is created, otherwise Service instance
Custom Dao, Connection can not be created directly, you need to get hold of the current thread Connection.
2. Connection Pool Management
It used c3p0 connection pool to connect to the database.
2.1 c3po Configuration
root
root
com.mysql.jdbc.Driver
jdbc:mysql://localhost:3306/learn-jdbc?characterEncoding=UTF-8
10
5
5
50
100
10
2.2 Database Connectivity Tools
Package and method of database connection resource database connections closed
public class DbConnUtil {
// c3P0 configuration name
private static final String c3p0PoolName = "myC3p0Pool";
// configuration data source
private static final DataSource dataSource = new ComboPooledDataSource(c3p0PoolName);
// Configure the local connection
private static ThreadLocal txConnectionLocal = new ThreadLocal<>();
/ ** database connection
* @Param whether autoCommitTx open offer to commit the transaction
* @Return Connection database connection
* @since 1.0
* @author zongf
* @created 2019-07-18
*/
public static Connection getConnection(boolean autoCommitTx) {
try {
Connection connection = dataSource.getConnection();
connection.setAutoCommit(autoCommitTx);
return connection;
} catch (SQLException e) {
e.printStackTrace ();
}
return null;
}
/**
* @Description: get this thread connection
* @Return: Connection Database Connection
* @author: zongf
* @time: 2019-06-26 14:37:00
* @since 1.0
*/
public static TxConnection getTxConnection() {
TxConnection txConnection = null;
// If ThreadLocal connection is empty, create a new connection
if (txConnectionLocal.get() == null || txConnectionLocal.get().getConnection() == null) {
txConnection = new TxConnection(getConnection(true));
txConnectionLocal.set(txConnection);
} else {
txConnection = txConnectionLocal.get();
}
return txConnection;
}
/ ** database connection in the current thread
* @return Connection
* @since 1.0
* @author zongf
* @created 2019-07-18
*/
public static Connection getLocalConnection() {
return getTxConnection().getConnection();
}
/ ** database connection object for the current thread
* @return ThreadLocal
* @since 1.0
* @author zongf
* @created 2019-07-18
*/
public static ThreadLocal getLocalTxConnection() {
return txConnectionLocal;
}
/ ** When the return connection, you need to set auto-commit to true.
* @param connection
* @return null
* @since 1.0
* @author zongf
* @created 2019-07-18
*/
public static void release(Connection connection) throws SQLException {
try {
if (connection != null && !connection.isClosed()) {
connection.setAutoCommit(true);
}
} catch (SQLException e) {
e.printStackTrace ();
} finally {
connection.close();
}
}
}
2.3 Transaction defined connection object
Because nested transaction, which one needs to follow a dynamic proxy open transaction is rolled back by the opening and which layer dynamic agent in charge of affairs, and therefore need to record transactions opener. So I created a TxConnneciton object.
public class TxConnection {
private Connection connection;
private String creator;
// omitted setter / getter method
}
3. Customize the Transaction open comment
The definition of a similar spring @Transcation comment for opening transactions.
Propagation.REQUIRES_NEW openNewTx one of seven for the propagation of transaction simulation of spring
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableTranscation {
// whether to open a new transaction
boolean openNewTx() default false;
}
4. The dynamic proxy processor
/ ** Affairs dynamic proxy processor
* @since 1.0
* @author zongf
* @created 2019-07-18
*/
public class TranscationHandler implements InvocationHandler {
private Object target;
public TranscationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Get the current database connection
TxConnection txConnection = DbConnUtil.getTxConnection();
// Save the old connection objects
TxConnection oldTxConnection = null;
try {
// Get the target object methods
Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
// method to see whether the current open transaction
boolean enableTx = targetMethod.isAnnotationPresent(EnableTranscation.class);
// if open transactions, current connection is set to manual commit the transaction
if (enableTx) {
// Get the information notes
EnableTranscation annotation = targetMethod.getAnnotation(EnableTranscation.class);
// Get whether to open a new transaction
boolean openNewTx = annotation.openNewTx();
if (! txConnection.getConnection (). getAutoCommit ()) {// false, indicating that the transaction has been opened
if (openNewTx) {// If you need to open the transaction
// Save the original database connection
oldTxConnection = txConnection;
// Get a new connection
txConnection = new TxConnection(DbConnUtil.getConnection(false), this.toString());
// replace the database connection in the current thread
DbConnUtil.getLocalTxConnection().set(txConnection);
}
} Else {// true, it indicates that the transaction is not turned on
// no open transactions, set auto-commit to false. Said it has started business
txConnection.getConnection().setAutoCommit(false);
txConnection.setCreator(this.toString());
}
}
// execute the target method
Object object = targetMethod.invoke(this.target, args);
// If the transaction is the current handler object is created, then commit the transaction
if (this.toString().equals(txConnection.getCreator())) {
txConnection.getConnection().commit();
}
return object;
} catch (Exception e) {
if (txConnection != null && this.toString().equals(txConnection.getCreator())) {
if (txConnection.getConnection() != null && !txConnection.getConnection().isClosed()) {
txConnection.getConnection().rollback();
txConnection.getConnection().setAutoCommit(true);
}
}
throw new RuntimeException ( "exception occurs, the transaction has been rolled back!", e);
} finally {
// release the database connection
if (txConnection != null && this.toString().equals(txConnection.getCreator())) {
DbConnUtil.release(txConnection.getConnection());
}
// If the new connection is not null, it means opening a new business. Rollback original connection
if (oldTxConnection != null) {
DbConnUtil.getLocalTxConnection().set(oldTxConnection);
}
}
}
}
5. ServiceFactory Factory
Creating Service factory class, used to simulate the Spring container. When the target Service included @EnableTransaction notes, create a dynamic proxy Service, or the creation Service object.
/ ** Service plant simulation spring containers
* @since 1.0
* @author zongf
* @created 2019-07-18
*/
public class ServiceFactory {
/ ** Gets Service instances
* @Param clz Service implementation class type
* @Return T Service objects or dynamic proxy object
* @since 1.0
* @author zongf
* @created 2019-07-18
*/
public static T getService(Class clz) {
T t = null;
try {
t = clz.newInstance();
} catch (Exception e) {
e.printStackTrace ();
throw new RuntimeException ( "Failed to create object");
}
// judgments can not be an interface, the interface implementation class can not be created
if(clz.isInterface()){
throw new RuntimeException ( "interface can not create an instance!");
}
// whether to open dynamic proxies
boolean enableTx = false;
// loop through all non-private methods, if the method has @EnableTx notes, then you need to create a proxy
Method[] methods = clz.getMethods();
for (Method method : methods) {
if (method.getAnnotation(EnableTranscation.class) != null) {
enableTx = true;
break;
}
}
// If you need to create a proxy, the proxy object is returned
if (enableTx) {
return (T) Proxy.newProxyInstance(clz.getClassLoader(), clz.getInterfaces(), new TranscationHandler(t));
}
return t;
}
}
6. declarative transaction test
Test, written before the author BaseDao to simplify development by means of basic steps.
1.1 Definition Interface
public interface IMixService {
// simulate normal
void success();
// simulate abnormal operation, transaction rollback
void error();
void show();
}
6.2 define the implementation class
public class MixService implements IMixService {
private IUserService userService = ServiceFactory.getService(UserService.class);
private IPersonService personService = ServiceFactory.getService(PersonService.class);
@EnableTranscation
@Override
public void success() {
this.userService.save(new UserPO("user-01", "123456"));
this.personService.save(new PersonPO("person-01", "abcdefg"));
} Wuxi Women's Hospital http://www.bhnnk120.com/
@EnableTranscation
@Override
public void error() {
this.userService.save(new UserPO("user-01", "123456"));
this.personService.save(new PersonPO("person-01", "abcdefg"));
// simulate abnormal Hall
int a = 1/0;
}
@Override
public void show() {
List userPOS = this.userService.queryAll();
List personPOS = this.personService.queryAll();
System.out.println("\n****** t_user: *****");
userPOS.forEach(System.out::println);
System.out.println("\n****** t_person: *****");
personPOS.forEach(System.out::println);
}
}
6.3 Test Case
public class MixService implements IMixService {
private IUserService userService = ServiceFactory.getService(UserService.class);
private IPersonService personService = ServiceFactory.getService(PersonService.class);
@EnableTranscation
@Override
public void success() {
this.userService.save(new UserPO("user-01", "123456"));
this.personService.save(new PersonPO("person-01", "abcdefg"));
}
@EnableTranscation
@Override
public void error() {
this.userService.save(new UserPO("user-01", "123456"));
this.personService.save(new PersonPO("person-01", "abcdefg"));
// simulate abnormal Hall
int a = 1/0;
}
@Override
public void show() {
List userPOS = this.userService.queryAll();
List personPOS = this.personService.queryAll();
System.out.println("\n****** t_user: *****");
userPOS.forEach(System.out::println);
System.out.println("\n****** t_person: *****");
personPOS.forEach(System.out::println);
}
}