【Java EE 学习 24 下】【注解在数据库开发中的使用】【反射+注解+动态代理在事务中的应用service层】
一、使用注解可以解决JavaBean和数据库中表名不一致、字段名不一致、字段数量不一致的问题。
1.Sun公司给jdbc提供的注解
@Table、@Column、@Id、@OneToMany、@OneToOne、@ManyToMany
2.小练习:对JavaBean的某些字段进行注解、对JavaBean名称进行注解以匹配数据库表名。
Person.java
package com.kdyzm.domain; import javax.persistence.Column; import javax.persistence.Table; /* * 使用注解可以解决数据库表名和类名不一致的问题,可以解决字段名不匹配的问题,可以解决字段数量不匹配的问题 */ @Table(name="user")//实际的表名为user,而不是person public class Person { @Column(name="id") private String personid; @Column(name="name") private String personname; @Column(name="age") private int personage; private String personAddress;//该字段在数据库表中斌不存在。 public Person() { } public Person(String personid, String personname, int personage, String personAddress) { this.personid = personid; this.personname = personname; this.personage = personage; this.personAddress = personAddress; } public String getPersonid() { return personid; } public void setPersonid(String personid) { this.personid = personid; } public String getPersonname() { return personname; } public void setPersonname(String personname) { this.personname = personname; } public int getPersonage() { return personage; } public void setPersonage(int personage) { this.personage = personage; } public String getPersonAddress() { return personAddress; } public void setPersonAddress(String personAddress) { this.personAddress = personAddress; } @Override public String toString() { return "Person [personid=" + personid + ", personname=" + personname + ", personage=" + personage + "]"; } }
UserDao.java
1 package com.kdyzm.dao; 2 3 import com.kdyzm.dbutils.DataSourceUtils_c3p0; 4 import com.kdyzm.dbutils.QueryRunner; 5 import com.kdyzm.domain.Person; 6 7 //注解在数据库中的使用方法 8 public class UserDao { 9 public static void main(String[] args) throws Exception { 10 Person p=new Person(); 11 p.setPersonid("12455324"); 12 p.setPersonname("小强"); 13 p.setPersonage(24); 14 p.setPersonAddress("山东理工大学"); 15 16 QueryRunner run=new QueryRunner(DataSourceUtils_c3p0.getDataSource()); 17 run.save(p); 18 } 19 }
重写QueryRunner类(dbutils.jar包)
package com.kdyzm.dbutils; /* * 重写QueryRunner类,将异常全部捕捉 * 新增加一个方法save,这个方法利用反射机制可以省去书写sql的麻烦。 */ import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import javax.persistence.Column; import javax.persistence.Table; import javax.sql.DataSource; import org.apache.commons.dbutils.ResultSetHandler; public class QueryRunner extends org.apache.commons.dbutils.QueryRunner{ public <T>T save(T t)throws Exception { Class<?> cls=t.getClass(); /* * 获取表名,这里理应当先进行判断是否存在注解即调用isAnnotationPresents方法, * 但是因为一定存在注解,所以不进行判断了 */ Table table=cls.getAnnotation(Table.class); //首先获取表名 String tableName=table.name(); //组成sql语句,这是重中自重 String sql="insert into "+tableName; String columns="(";//拼接字段名称 String values="values(";//拼接值 //获取所有声明的字段名称 Field[]fields=cls.getDeclaredFields(); for(Field field:fields) { if(field.isAnnotationPresent(Column.class))//如果进行了注解的话,这句话是解决字段数量不匹配的核心 { field.setAccessible(true);//设置可以暴力访问 String columnName=field.getName();//本来的字段名 Column column=field.getAnnotation(Column.class); //优先考虑有没有注解的字段名,如果没有注解的话则使用原来的字段名否则使用注解声明的字段名 if(column.name()!=null||!column.name().equals(""))//如果有注解的话使用注解上声明的字段名 { columnName=column.name(); } //获取列值 Object value=field.get(t); if(columns.equals("(")) { columns+=columnName; if(value instanceof String) { values+="'"+value+"'"; } else { values+=value; } } else { columns+=","+columnName; if(value instanceof String) { values+=",'"+value+"'"; } else { values+=","+value; } } } } columns+=") "; values+=")"; sql+=columns; sql+=values; System.out.println(sql); update(sql); return t; } public QueryRunner() { super(); } public QueryRunner(boolean pmdKnownBroken) { super(pmdKnownBroken); } public QueryRunner(DataSource ds, boolean pmdKnownBroken) { super(ds, pmdKnownBroken); } public QueryRunner(DataSource ds) { super(ds); } @Override public int[] batch(Connection conn, String sql, Object[][] params) throws SQLException { return super.batch(conn, sql, params); } @Override public int[] batch(String sql, Object[][] params) throws SQLException{ return super.batch(sql, params); } @Override public <T> T insert(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException { return super.insert(conn, sql, rsh, params); } @Override public <T> T insert(Connection conn, String sql, ResultSetHandler<T> rsh) throws SQLException { return super.insert(conn, sql, rsh); } @Override public <T> T insert(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException { return super.insert(sql, rsh, params); } @Override public <T> T insert(String sql, ResultSetHandler<T> rsh) throws SQLException { return super.insert(sql, rsh); } @Override public <T> T insertBatch(Connection conn, String sql, ResultSetHandler<T> rsh, Object[][] params) throws SQLException { return super.insertBatch(conn, sql, rsh, params); } @Override public <T> T insertBatch(String sql, ResultSetHandler<T> rsh, Object[][] params) throws SQLException { return super.insertBatch(sql, rsh, params); } @Override public <T> T query(Connection conn, String sql, Object param, ResultSetHandler<T> rsh) throws SQLException { return super.query(conn, sql, param, rsh); } @Override public <T> T query(Connection conn, String sql, Object[] params, ResultSetHandler<T> rsh) throws SQLException { return super.query(conn, sql, params, rsh); } @Override public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException { return super.query(conn, sql, rsh, params); } @Override public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh) throws SQLException { return super.query(conn, sql, rsh); } @Override public <T> T query(String sql, Object param, ResultSetHandler<T> rsh) throws SQLException { return super.query(sql, param, rsh); } @Override public <T> T query(String sql, Object[] params, ResultSetHandler<T> rsh) throws SQLException { return super.query(sql, params, rsh); } @Override public <T> T query(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException { return super.query(sql, rsh, params); } @Override public <T> T query(String sql, ResultSetHandler<T> rsh) throws SQLException { return super.query(sql, rsh); } @Override public int update(Connection conn, String sql, Object... params) throws SQLException { return super.update(conn, sql, params); } @Override public int update(Connection conn, String sql, Object param) throws SQLException { return super.update(conn, sql, param); } @Override public int update(Connection conn, String sql) throws SQLException { return super.update(conn, sql); } @Override public int update(String sql, Object... params) throws SQLException { return super.update(sql, params); } @Override public int update(String sql, Object param) throws SQLException { return super.update(sql, param); } @Override public int update(String sql) throws SQLException { return super.update(sql); } @Override protected void close(Connection conn) throws SQLException { super.close(conn); } @Override protected void close(ResultSet rs) throws SQLException { super.close(rs); } @Override protected void close(Statement stmt) throws SQLException { super.close(stmt); } @Override public void fillStatement(PreparedStatement arg0, Object... arg1) throws SQLException { super.fillStatement(arg0, arg1); } @Override public void fillStatementWithBean(PreparedStatement arg0, Object arg1, PropertyDescriptor[] arg2) throws SQLException { super.fillStatementWithBean(arg0, arg1, arg2); } @Override public void fillStatementWithBean(PreparedStatement arg0, Object arg1, String... arg2) throws SQLException { super.fillStatementWithBean(arg0, arg1, arg2); } @Override public DataSource getDataSource() { return super.getDataSource(); } @Override public boolean isPmdKnownBroken() { return super.isPmdKnownBroken(); } @Override protected Connection prepareConnection() throws SQLException { return super.prepareConnection(); } @Override protected PreparedStatement prepareStatement(Connection conn, String sql, int returnedKeys) throws SQLException { return super.prepareStatement(conn, sql, returnedKeys); } @Override protected PreparedStatement prepareStatement(Connection conn, String sql) throws SQLException { return super.prepareStatement(conn, sql); } @Override protected void rethrow(SQLException cause, String sql, Object... params) throws SQLException { super.rethrow(cause, sql, params); } @Override protected ResultSet wrap(ResultSet rs) { return super.wrap(rs); } }
重写之后的类中多出了一个方法:save方法,该方法接受一个JavaBean参数,并使用反射对该JavaBean进行解析,得到该JavaBean中符合要求的字段,并得到每个字段对应的值,关键是凭借sql语句的过程。
C3p0数据库连接池工具类:
package com.kdyzm.dbutils; /** * 数据库连接池工具类。 */ import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; public class DataSourceUtils_c3p0 { private static ThreadLocal<Connection>tl=new ThreadLocal<Connection>(); private static DataSource ds=null; static{ ds=new ComboPooledDataSource("namedconfig"); } public static Connection getConnection(){ Connection conn=tl.get(); if(conn==null) { try { conn=ds.getConnection(); tl.set(conn);//这句代码十分重要,千万不能忘(将当前线程和请求的Connection对象绑定) } catch (SQLException e) { e.printStackTrace(); } } return conn; } public static DataSource getDataSource(){ return ds; } public static void remove(){ tl.remove();//这句代码也十分重要,千万不能忘掉,将会在TransactionFilter中调用 } }
c3p0数据库连接池配置文件:
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <!-- 默认配置,只可以出现一次 --> <default-config> <!-- 连接超时设置30秒 --> <property name="checkoutTimeout">30000</property> <!-- 30秒检查一次connection的空闲 --> <property name="idleConnectionTestPeriod">30</property> <!--初始化的池大小 --> <property name="initialPoolSize">2</property> <!-- 最多的一个connection空闲时间 --> <property name="maxIdleTime">30</property> <!-- 最多可以有多少个连接connection --> <property name="maxPoolSize">10</property> <!-- 最少的池中有几个连接 --> <property name="minPoolSize">2</property> <!-- 批处理的语句--> <property name="maxStatements">50</property> <!-- 每次增长几个连接 --> <property name="acquireIncrement">3</property> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl"> <![CDATA[jdbc:mysql://10.6.112.200:3306/test?useUnicode=true&characterEncoding=UTF-8]]> </property> <property name="user">root</property> <property name="password">5a6f38</property> </default-config> <named-config name="namedconfig"> <!-- 连接超时设置30秒 --> <property name="checkoutTimeout">30000</property> <!-- 30秒检查一次connection的空闲 --> <property name="idleConnectionTestPeriod">30</property> <!--初始化的池大小 --> <property name="initialPoolSize">2</property> <!-- 最多的一个connection空闲时间 --> <property name="maxIdleTime">30</property> <!-- 最多可以有多少个连接connection --> <property name="maxPoolSize">4</property> <!-- 最少的池中有几个连接 --> <property name="minPoolSize">2</property> <!-- 批处理的语句--> <property name="maxStatements">50</property> <!-- 每次增长几个连接 --> <property name="acquireIncrement">2</property> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl"> <![CDATA[jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8]]> </property> <property name="user">root</property> <property name="password">5a6f38</property> </named-config> </c3p0-config>
3.运行结果:
2015-7-1 20:10:05 com.mchange.v2.log.slf4j.Slf4jMLog$Slf4jMLogger$InfoLogger log 信息: MLog clients using slf4j logging. 2015-7-1 20:10:07 com.mchange.v2.log.slf4j.Slf4jMLog$Slf4jMLogger$InfoLogger log 信息: Initializing c3p0-0.9.5 [built 02-January-2015 13:25:04 -0500; debug? true; trace: 10] insert into user(id,name,age) values('12455324','小强',24) 2015-7-1 20:10:08 com.mchange.v2.log.slf4j.Slf4jMLog$Slf4jMLogger$InfoLogger log 信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 2, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 30000, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> namedconfig, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceUseNamedDriverClass -> false, identityToken -> 2s4ze59akeetzj1p6tfdu|a32b, idleConnectionTestPeriod -> 30, initialPoolSize -> 2, jdbcUrl -> jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 30, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 4, maxStatements -> 50, maxStatementsPerConnection -> 0, minPoolSize -> 2, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
数据库中已经多出了一条记录。表名已经保存到数据库中成功。
4.源代码:https://github.com/kdyzm/day24_2
二、反射+注解+动态代理在事务中的应用service层
1.代理
代理就使用一种方法在一个对象调用一个方法的时候拦截该方法的执行并改为执行另外一个动作。
2.代理的核心类
(1)Proxy:在内存中生成该接口的一个实例。
(2)InvocationHandler:执行句柄,在执行代理类的方法的时候,该句柄会拦截所有代理类的方法。
3.使用代理类的要求:被代理类必须至少实现一种接口。
4.对List进行代理。
package com.kdyzm.demo; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; /** * 演示对List进行代理,只能对List进行代理。 * @author kdyzm * */ public class ProxyForListDemo { public static void main(String[] args) { final List<String>list=new ArrayList<String>(); Object proxy=Proxy.newProxyInstance(ProxyForListDemo.class.getClassLoader(), new Class[]{List.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getName()+"方法被调用!"); return method.invoke(list, args); } }); @SuppressWarnings("unchecked") List<String>l=(List<String>) proxy; l.add("小强!"); System.out.println(l); } }
运行结果:
add方法被调用!
toString方法被调用!
[小强!]
三、对所实现接口的类进行代理
1.使用一个类封装代理的过程。该类实现了InvocationHandler接口。
package com.kdyzm.demo; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /* * 演示对所有拥有接口的类进行代理,并且该类实现了InvocationHandler接口。 * 这种方式是推荐的代理方式。 */ public class ProxyForAllClassHasInterface implements InvocationHandler{ private Object src; private ProxyForAllClassHasInterface(Object src) { this.src=src; } public static Object factory(Object src) { Object aim=Proxy.newProxyInstance(ProxyForAllClassHasInterface.class.getClassLoader(), src.getClass().getInterfaces(), new ProxyForAllClassHasInterface(src)); return aim; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getName()+"方法被调用ProxyForAllClassHasInterface!"); return method.invoke(src, args);//调用src的方法。 } }
这种方式是推荐的使用方式。
2.测试该工具类。
package com.kdyzm.demo; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /* * 对ProxyForAllClassHasInterface类进行测试! */ public class Test { public static void main(String[] args) { List<String>list=new ArrayList<String>(); list=(List<String>) ProxyForAllClassHasInterface.factory(list); list.add("你好,小强!"); System.out.println(list); Map<String,String>map=new HashMap<String,String>(); map=(Map<String, String>) ProxyForAllClassHasInterface.factory(map); map.put("12110501001", "小强"); System.out.println(map); } }
3.运行结果
add方法被调用ProxyForAllClassHasInterface!
toString方法被调用ProxyForAllClassHasInterface!
[你好,小强!]
put方法被调用ProxyForAllClassHasInterface!
toString方法被调用ProxyForAllClassHasInterface!
{12110501001=小强}
四、反射+注解+动态代理在事务中的应用service层
1.解决的问题:事务使用OSIV模式并不高效,而且结构比较复杂,为了解决这个问题,可以使用反射+注解+动态代理的方式,这将称为最终的解决方式。
使用该方式的灵活性极高,事务的处理过程在service层解决,但是在serviece层的代码中又看不出来,实际上的事务处理在代理类中实现,service是否开启事务,仅仅只需要一句代码就可以解决。
2.核心类:代理service的ProxyForTransactionService类。
package com.kdyzm.dbutils; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Connection; import com.kdyzm.demo.ProxyForAllClassHasInterface; import com.kdyzm.myannotation.Transactionflag; /* * 对Service类进行代理,拦截特定的方法并进行修改,实现InvocationHandler接口是经典的做法。 * 该类可以放在工具类中。 */ public class ProxyForTransactionService implements InvocationHandler{ private Object src; private ProxyForTransactionService(Object src) { this.src=src; } public static Object factory(Object src) { Object aim=Proxy.newProxyInstance(ProxyForAllClassHasInterface.class.getClassLoader(), src.getClass().getInterfaces(), new ProxyForTransactionService(src)); return aim; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Connection connection=null; Object returnValue=null; if(!method.isAnnotationPresent(Transactionflag.class))//首先判断是否是经过注解的方法,如果是没有经过注解的方法则表明不需要开启事务! { System.out.println("不需要开启事务的方法:"+method.getName()); return method.invoke(src, args); } System.out.println("需要开启事务的方法:"+method.getName()); try { //获取连接 connection=DataSourceUtils_c3p0.getConnection(); //设置事务的开始 connection.setAutoCommit(false); System.out.println("已经开启事务!"); //调用方法 method.invoke(src, args); connection.commit(); System.out.println("提交!结束事务!"); } catch(Exception e) { connection.rollback(); System.out.println("回滚!结束事务!"); throw e;//为什么能抛,因为Throwable是Exception的父类。 } finally { connection.close(); DataSourceUtils_c3p0.remove(); } return returnValue; } }
2.要对service层的类进行代理,这些类必须至少实现一个接口,所以需要定义一个接口。
package com.kdyzm.interfaces; import com.kdyzm.myannotation.Transactionflag; public interface TransactionInterface { @Transactionflag//表示该方法需要使用事务 public void save();//定义save事务解决方法。 //不加注解表示该方法不需要使用事务。 public void query(); }
3.实现该接口的类看不出有事务的处理,但实际上已经对某些方法开启了事务。
package com.kdyzm.service; import com.kdyzm.dao.Dao1; import com.kdyzm.dao.Dao2; import com.kdyzm.interfaces.TransactionInterface; public class SaveService implements TransactionInterface{ Dao1 dao1=new Dao1(); Dao2 dao2=new Dao2(); @Override public void save() { dao1.save(); dao2.save(); } @Override public void query() { dao1.query(); } }
4.自定义一个注解对接口上的某些方法进行注解。
package com.kdyzm.myannotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(value=RetentionPolicy.RUNTIME) @Target(value={ElementType.METHOD}) public @interface Transactionflag { }
5.测试
(1)当没有异常发生的时候
控制台输出结果:
需要开启事务的方法:save 七月 01, 2015 9:05:20 下午 com.mchange.v2.log.MLog 信息: MLog clients using java 1.4+ standard logging. 七月 01, 2015 9:05:21 下午 com.mchange.v2.c3p0.C3P0Registry 信息: Initializing c3p0-0.9.5 [built 02-January-2015 13:25:04 -0500; debug? true; trace: 10] 七月 01, 2015 9:05:21 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource 信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 2, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 30000, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> namedconfig, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceUseNamedDriverClass -> false, identityToken -> 2s4ze59akgdv042f54zv|142f828, idleConnectionTestPeriod -> 30, initialPoolSize -> 2, jdbcUrl -> jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 30, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 4, maxStatements -> 50, maxStatementsPerConnection -> 0, minPoolSize -> 2, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ] 已经开启事务! 提交!结束事务!
检查数据库,多出了两条记录。
(2)当有异常发生时。
需要开启事务的方法:save 七月 01, 2015 9:07:05 下午 com.mchange.v2.log.MLog 信息: MLog clients using java 1.4+ standard logging. 七月 01, 2015 9:07:05 下午 com.mchange.v2.c3p0.C3P0Registry 信息: Initializing c3p0-0.9.5 [built 02-January-2015 13:25:04 -0500; debug? true; trace: 10] 七月 01, 2015 9:07:05 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource 信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 2, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 30000, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> namedconfig, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceUseNamedDriverClass -> false, identityToken -> 2s4ze59akgg3q3p33eio|142f828, idleConnectionTestPeriod -> 30, initialPoolSize -> 2, jdbcUrl -> jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 30, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 4, maxStatements -> 50, maxStatementsPerConnection -> 0, minPoolSize -> 2, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ] 已经开启事务! 回滚!结束事务! 七月 01, 2015 9:07:06 下午 org.apache.catalina.core.StandardWrapperValve invoke
各种异常信息略。
检查数据库,一条记录也没有。
5.怎样判别一个方法是否需要开启事务?比如查询操作就不需要开启事务。
解决方法:对需要进行事务处理的方法进行注解,在代理工具类中进行判断。
6.源代码:https://github.com/kdyzm/day24_3
7.注意事项:代理之后的对象强制转换的结果一定是被代理类的接口的实例,而不是被代理类的实例。这点是十分重要的。如果强转成被代理类的实例,则一定会强制转换异常的错误。