Java回顾之Spring基础
我计划分两到三篇文章来描述Spring,这一篇主要讲Spring一些基础的内容。
概述
Spring是一个非常火的框架,尤其是在Web开发领域,和Struts以及Hibernate构成了SSH三剑客。当时Web开发的另一个组合是LAMP,即Linux+Apache+MySQL+PHP。我在前端方面基本没有实战经验,对js等技术也还是停留在概念和语法方面,所以扬长避短,我对Spring以及Hibernate特别感兴趣。
当年Spring是作为EJB的“替代者”横空出世的,其创始人Rod Johnson还写了一本《J2EE development without EJB》来推行这个框架,这也是一本关于Spring很经典的书,不过最好是在接触Spring一段时间后再去阅读,效果会好一点。
Spring最主要的特点有两个:IoC和AOP,这也是J2EE开发企业软件时经常碰到的问题:1)对象太多如何管理;2)共同逻辑和业务逻辑纠缠在一起,错综复杂,如何解耦。
这篇文章主要关注3个方面:IoC、AOP和数据库访问。这里我们假设所有需要的jar都已经准备就绪。
IoC
IoC的全称是Inversion of Control,中文称为控制反转, Martin Flower由根据它创造了一个新词:Dependency Injection,中文称为依赖注入。这两个词讲的是一回事儿。
IoC的实质是如何管理对象,传统意义上我们使用new方式来创建对象,但在企业应用开发的过程中,大量的对象创建都在程序中维护很容易造成资源浪费,并且不利于程序的扩展。
实现IoC通常有三种方式:
1)利用接口或者继承,一般以接口较多。这种实现方式和我们平时提到的lazy load有异曲同工之妙。
2)构造函数注入。
3)属性注入。
IoC是Spring框架的核心,接下来我们来探索一下Spring中IoC的风采。
IoC简单示例
我们先来定义一个简单的接口和实现:
public interface UserDao { void save(); } public class UserDaoImpl implements UserDao { public void save() { System.out.println("save() is called."); } }
然后是在classpath下创建一个beans.xml文件(这个文件名不是必须这样的):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <bean id="userDaoImpl" class = "sample.spring.ioc.UserDaoImpl"/> </beans>
接下来是测试代码:
private static void test1() { ApplicationContext ctx = new ClassPathXmlApplicationContext("sample/spring/ioc/beans.xml"); UserDao userDao = (UserDao)ctx.getBean("userDaoImpl"); userDao.save(); }
输出结果如下:
save() is called.
我们还可以通过工厂方式来创建对象。
通过静态工厂创建Bean
添加一个类,如下:
public class UserDaoFactory { public static UserDao getUserDao() { return new UserDaoImpl(); } }
在beans.xml中,添加如下内容:
1 <bean id="userDaoImpl2" class = "sample.spring.ioc.UserDaoFactory" factory-method = "getUserDao"/>
测试代码和执行结果和上面类似,不再赘述。
通过实例工厂创建Bean
添加如下类:
public class UserDaoFactory2 { public UserDao getUserDao() { return new UserDaoImpl(); } }
这个类和UserDaoFactory唯一的区别是这里的getUserDao是实例方法,而不是静态方法。
在beans.xml中追加如下内容:
1 <bean id="factory" class="sample.spring.ioc.UserDaoFactory2"/>
2 <bean id="userDaoImpl3" factory-bean="factory" factory-method="getUserDao"/>
测试方法和结果同上。
对象的生命周期
我们可以通过设置bean节点的scope属性来控制对象的声明周期,它包含两个可选值:
1)singleton,表明系统中对于同一个对象,只保留一个实例。
2)prototype,表明系统中每次获取bean时,都新建一个对象。
我们修改beans.xml文件:
1 <bean id="userDaoImpl" class = "sample.spring.ioc.UserDaoImpl" scope="singleton"/> 2 <bean id="userDaoImpl2" class = "sample.spring.ioc.UserDaoImpl" scope="prototype"/>
这两个bean指向同一个类型,但是scope的设置不同。
下面是测试方法:
private static void scopeTest() { ApplicationContext ctx = new ClassPathXmlApplicationContext("sample/spring/ioc/scope.xml"); System.out.println("=====Singleton test====="); UserDao userDao1A = (UserDao)ctx.getBean("userDaoImpl"); UserDao userDao1B = (UserDao)ctx.getBean("userDaoImpl"); System.out.println("userDao1A == userDao1B:" + (userDao1A==userDao1B)); System.out.println("=====Prototype test====="); UserDao userDao2A = (UserDao)ctx.getBean("userDaoImpl2"); UserDao userDao2B = (UserDao)ctx.getBean("userDaoImpl2"); System.out.println("userDao2A == userDao2B:" + (userDao2A==userDao2B)); }
执行结果如下:
=====Singleton test===== userDao1A == userDao1B:true =====Prototype test===== userDao2A == userDao2B:false
如何设置对象属性
上面的示例中,我们的对象中没有包含属性,对于业务对象来说,这一般是不现实。实际中的对象或多或少都会有一些属性。
Spring支持两种方式对属性赋值:set方式和构造函数。
下面我们会分别描述两种方式,但首先我们需要展示业务对象:
public class UserServiceBean { private int userID; private String userName; private UserDao userDao; private List<String> hobbies; private Map<String, Integer> scores; public UserServiceBean(int userID, String userName, UserDao userDao, List hobbies,Map scores) { this.userID = userID; this.userName = userName; this.userDao = userDao; this.hobbies = hobbies; this.scores = scores; } public UserServiceBean(){} public void setUserID(int userID) { this.userID = userID; } public int getUserID() { return userID; } public void setUserName(String userName) { this.userName = userName; } public String getUserName() { return userName; } public void setUserDao(UserDao userDao) { this.userDao = userDao; } public UserDao getUserDao() { return userDao; } public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } public List<String> getHobbies() { return hobbies; } public void setScores(Map<String, Integer> scores) { this.scores = scores; } public Map<String, Integer> getScores() { return scores; } } 定义UserServiceBean
这是一个典型的学生信息,包括学号、姓名、爱好和成绩。
通过Set方式为对象属性赋值
我们在beans.xml中追加如内容:
<bean id="userService" class="sample.spring.ioc.UserServiceBean"> <property name="userID" value="1"/> <property name="userName" value="张三"/> <property name="userDao" ref="userDaoImpl"/> <property name="hobbies"> <list> <value>羽毛球</value> <value>看电影</value> <value>弹吉他</value> </list> </property> <property name="scores"> <map> <entry key="数据结构" value="90"/> <entry key="编译原理" value="85"/> <entry key="离散数学" value="82"/> </map> </property> </bean>
上面是典型的为属性赋值的示例,其中属性不仅包括简单属性(整数、字符串),也包含了复杂属性(List、Map),还有其他的bean。
下面是测试代码:
private static void propertyTest1() { ApplicationContext ctx = new ClassPathXmlApplicationContext("sample/spring/ioc/beans.xml"); UserServiceBean userService = (UserServiceBean)ctx.getBean("userService"); printUserService(userService); } private static void printUserService(UserServiceBean userService) { System.out.println("编号:" + userService.getUserID()); System.out.println("姓名:" + userService.getUserName()); System.out.println("爱好:"); for(String hobby:userService.getHobbies()) { System.out.println(hobby); } System.out.println("学习成绩:"); for(Entry<String,Integer> entry:userService.getScores().entrySet()) { System.out.println(entry.getKey() + "\t" + entry.getValue()); } userService.getUserDao().save(); }
输出结果如下:
编号:1 姓名:张三 爱好: 羽毛球 看电影 弹吉他 学习成绩: 数据结构 90 编译原理 85 离散数学 82 save() is called.
通过构造函数为对象属性赋值
我们也可以通过构造函数来为对象赋值,在上面定义UserServiceBean时,我们已经添加了一个构造函数。下面来看beans.xml中的配置:
<bean id="userService2" class="sample.spring.ioc.UserServiceBean">
<constructor-arg index="0" value="1"/>
<constructor-arg index="1" value="张三"/>
<constructor-arg index="2" ref="userDaoImpl"/>
<constructor-arg index="3">
<list>
<value>羽毛球</value>
<value>看电影</value>
<value>弹吉他</value>
</list>
</constructor-arg>
<constructor-arg index="4">
<map>
<entry key="数据结构" value="90"/>
<entry key="编译原理" value="85"/>
<entry key="离散数学" value="82"/>
</map>
</constructor-arg>
</bean>
测试代码和输出结果同上。
需要注意:我们定义的业务对象应该保留默认的构造函数。
使用Annotation来定位Bean
在Spring中,除了在xml配置文件中定义对象,我们还可以使用Annotation来定位,这位我们提供了很大的方便。
这里我们使用的Annotation主要包括:@Resource/@Autowried/@Qualifier。
来看下面的示例:
public class UserServiceBean2 { private String userID; private String userName; @Resource(name="userDaoImpl") private UserDao userDao1; private UserDao userDao2; @Autowired(required=false) @Qualifier("userDaoImpl") private UserDao userDao3; @Autowired(required=false) @Qualifier("userDaoImpl3") private UserDao userDao4; public void setUserID(String userID) { this.userID = userID; } public String getUserID() { return userID; } public void setUserName(String userName) { this.userName = userName; } public String getUserName() { return userName; } @Resource public void setUserDao2(UserDao userDao2) { this.userDao2 = userDao2; } public UserDao getUserDao2() { return userDao2; } public void test() { userDao1.save(); userDao2.save(); System.out.println(userDao3.getClass().getName()); userDao3.save(); } } Annotation示例
测试方法:
private static void annotationTest() { ApplicationContext ctx = new ClassPathXmlApplicationContext("sample/spring/ioc/annotation.xml"); UserServiceBean2 userService = (UserServiceBean2)ctx.getBean("userService"); userService.test(); }
输出结果如下:
save() is called. save() is called. sample.spring.ioc.UserDaoImpl save() is called.
我们来对上面示例中出现的Annotation来进行说明。
1 @Resource(name="userDaoImpl") 2 private UserDao userDao1;
1 @Resource 2 public void setUserDao2(UserDao userDao2) { 3 this.userDao2 = userDao2; 4 }
这是作用在setter上的Annotation,@Resource 可以不写明name参数,这时Spring会首先按照名字然后按照数据类型的方式去定位bean。
自动加载对象定义
对于大型系统来说,我们可能会创建大量的类,如果这些类的声明都需要写在xml文件里的话,会产生额外大量的工作。
Spring提供了一种简单的机制让我们的对象可以自动注册。
我们可以在beans.xml中添加如下内容:
<context:component-scan base-package="sample.spring.ioc"/>
然后我们可以在sample.spring.ioc包下的对象,添加@Component/@Service/@Controller/@repository,这样Spring会自动将带有这些Annotation的类进行注册。
下面是一个示例:
@Service("userService") public class UserServiceBean3 { private String userID; private String userName; @Resource(name="userDaoImpl") private UserDao userDao1; @Autowired(required=true) @Qualifier("userDaoImpl") private UserDao userDao3; public void setUserID(String userID) { this.userID = userID; } public String getUserID() { return userID; } public void setUserName(String userName) { this.userName = userName; } public String getUserName() { return userName; } // @Resource // public void setUserDao2(UserDao userDao2) { // this.userDao2 = userDao2; // } // public UserDao getUserDao2() { // return userDao2; // } public void test() { userDao1.save(); // userDao2.save(); System.out.println(userDao3.getClass().getName()); userDao3.save(); } } 自动注册Bean示例
这个类和上面定义的UserServiceBean2非常相似,需要注意在类前面添加的Annotation信息。
我们不需要在xml文件中手动定义这个bean,Spring会进行自动注册,注册的bean名称是userService。
AOP
我们在Java回顾之反射中已经设计了一个简单的AOP框架,通常情况下,对于AOP,我们有两种方式来实现。
使用DynamicProxy实现AOP
下面是一个简单的示例,首先定义业务对象:
1 public interface UserDao { 2 3 void save(); 4 } 5 6 public class UserDaoImpl implements UserDao 7 { 8 private String name; 9 10 public void save() { 11 System.out.println("save() is called for " + name); 12 } 13 14 public void setName(String name) { 15 this.name = name; 16 } 17 18 public String getName() { 19 return name; 20 } 21 }
下面是一个实现了InvocationHandler的类:
1 public class ProxyFactory implements InvocationHandler 2 { 3 private Object target; 4 5 public Object createUserDao(Object target) 6 { 7 this.target = target; 8 return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), 9 this.target.getClass().getInterfaces(), this); 10 } 11 12 public Object invoke(Object proxy, Method method, Object[] args) 13 throws Throwable { 14 15 UserDaoImpl userDao = (UserDaoImpl)target; 16 Object result = null; 17 if(userDao.getName() != null) 18 { 19 result = method.invoke(target, args); 20 } 21 else 22 { 23 System.out.println("The name is null."); 24 } 25 return result; 26 } 27 }
接下来是测试代码:
1 private static void test1() 2 { 3 ProxyFactory pf = new ProxyFactory(); 4 UserDao userDao = (UserDao)pf.createUserDao(new UserDaoImpl()); 5 userDao.save(); 6 }
执行结果如下:
The name is null.
这是因为userDao的类型是UserDao,它是一个接口,并没有定义name字段,因此name=null。
使用Cglib实现AOP
同样是上面的需求,我们假设并没有继承的接口,这我们可以使用cglib来实现。
首先我们重新定义一个UserDaoImpl2,它不会实现任何接口:
public class UserDaoImpl2 { private String name; public void save() throws InterruptedException { Thread.sleep(3000); System.out.println("save() is called for " + name); } public void setName(String name) { this.name = name; } public String getName() { return name; } public void raiseException() { throw new RuntimeException("This is test."); } }
然后是创建CglibFactory:
public class CglibFactory implements MethodInterceptor { private Object target; public Object createUserDao(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); } public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { UserDaoImpl2 userDao = (UserDaoImpl2)target; if (userDao.getName() != null) { return method.invoke(target, args); } else { System.out.println("The name is null."); } return null; } }
它实现了MethodInterceptor接口,其中包括intercept方法,这个方法就会通过反射的方式来触发目标方法,同时还可以添加一些其他处理。
下面是测试方法:
private static void test2() throws InterruptedException { CglibFactory cf = new CglibFactory(); UserDaoImpl2 temp = new UserDaoImpl2(); UserDaoImpl2 userDao = (UserDaoImpl2)cf.createUserDao(temp); userDao.save(); temp.setName("Zhang San"); userDao = (UserDaoImpl2)cf.createUserDao(temp); userDao.save(); }
输出结果如下:
The name is null. save() is called for Zhang San
使用Spring实现AOP
Spring框架集合了ProxyFactory和Cglib两种方式来实现AOP。
我们来看一个示例,还是使用上面定义的UserDaoImpl以及UserDaoImpl2。
首先需要定义一个interceptor:
@Aspect public class MyInterceptor { @Pointcut("execution (* sample.spring.aop.*.*(..))") public void anyMethod(){} @Before("anyMethod()") public void before() { System.out.println("Before"); } @After("anyMethod()") public void after() { System.out.println("After"); } @Around("anyMethod()") public void Around(ProceedingJoinPoint pjp) throws Throwable { long start = System.currentTimeMillis(); pjp.proceed(); long end = System.currentTimeMillis(); System.out.println("执行时间:" + (end - start)); } @Before("anyMethod() && args(name)") public void before(String name) { System.out.println("The name is " + name); } @AfterReturning(pointcut="anyMethod()", returning="result") public void afterReturning(String result) { System.out.println("The value is " + result); } @AfterThrowing(pointcut="anyMethod()", throwing="e") public void afterThrowing(Exception e) { e.printStackTrace(); } }
我们可以看到上面的代码中包含了一些Annotation,这些Annotation是用来实现AOP的关键。
然后需要修改beans.xml,添加如下内容:
1 <aop:aspectj-autoproxy /> 2 <bean id="userDaoImpl" class = "sample.spring.aop.UserDaoImpl"/> 3 <bean id="userDaoImpl2" class = "sample.spring.aop.UserDaoImpl2"/> 4 <bean id="myInterceptor" class="sample.spring.aop.MyInterceptor"/>
其中第一行是让Spring打开AOP的功能,下面三行定义了三个bean,这里我们把interceptor也看做是一个bean对象。
接下来是测试代码:
private static void test3() throws InterruptedException { ApplicationContext ctx = new ClassPathXmlApplicationContext("sample/spring/aop/beans.xml"); UserDao userDao = (UserDao)ctx.getBean("userDaoImpl"); userDao.save(); UserDaoImpl2 userDao2 = (UserDaoImpl2)ctx.getBean("userDaoImpl2"); userDao2.save(); userDao2.setName("Zhang San"); String name = userDao2.getName(); // userDao2.raiseException(); }
这里我们可以看到,测试方法中既使用了UserDaoImpl1(这里是UserDao接口),也是用了UserDaoImpl2。正如我们上面所言,在Spring中,如果类实现了接口,Spring会按照ProxyFactory的方式来处理;如果没有实现接口,Spring会按照Cglib的方式来处理。
上面测试方法的输出如下:
Before Before save() is called for null 执行时间:1 The value is null After After 执行时间:1 The value is null Before Before save() is called for null 执行时间:3001 The value is null After After 执行时间:3002 The value is null Before The name is Zhang San Before 执行时间:26 The value is null After After 执行时间:27 The value is null Before Before 执行时间:0 The value is null After After 执行时间:1 The value is null
使用Spring配置文件来配置AOP
上面的示例中,我们使用Annotation来配置AOP的信息,同样我们也可以使用xml文件的方式来配置AOP。
还是以上面定义的interceptor为基础,我们去掉里面所有的Annotation,然后在beans.xml中添加如下内容:
<bean id="myInterceptor2" class="sample.spring.aop.MyInterceptor2"/>
<aop:config>
<aop:aspect id="asp" ref="myInterceptor2">
<aop:pointcut id="anyMethod" expression="execution (* sample.spring.aop.*.*(..))"/>
<aop:before pointcut-ref="anyMethod" method="before"/>
<aop:after pointcut-ref="anyMethod" method="after"/>
<aop:around pointcut-ref="anyMethod" method="around"/>
<aop:after-returning pointcut-ref="anyMethod" method="afterReturning" returning="result"/>
<aop:after-throwing pointcut-ref="anyMethod" method="afterThrowing" throwing="e"/>
</aop:aspect>
</aop:config>
测试方法和输出结果同上。
Spring和JDBC
Spring中也包含了对JDBC数据访问的支持,它有一个JdbcTemplate的机制,其中提供了大量的API,对ResultSet进行了封装,可以大大简化我们的工作量。
同时Spring还提供了针对事务的支持,包含了一些Annotation,既可以作用在类上,也可以作用在方法上。
下面是一个简单的示例,我们还是连接MySQL数据库中的user表,实现对其CRUD操作。
首先是定义业务对象以及DAO接口:
public class User { private int userID; private String userName; public void setUserID(int userID) { this.userID = userID; } public int getUserID() { return userID; } public void setUserName(String userName) { this.userName = userName; } public String getUserName() { return userName; } } public interface UserDao { void insertUser(User user); void updateUser(User user); void deleteUser(User user); List<User> getAllUser(); User getUser(int id); }
然后是建立一个Dao的实现类,这里使用了一些JdbcTemplate API:
@Transactional public class UserDaoImpl implements UserDao { private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) throws SQLException { jdbcTemplate = new JdbcTemplate(dataSource); System.out.println(dataSource.getConnection().getMetaData().getDriverName()); } public void deleteUser(User user) { jdbcTemplate.update("delete from user where id=?", new Object[]{user.getUserID()}, new int[]{java.sql.Types.INTEGER}); } @SuppressWarnings("unchecked") public List<User> getAllUser() { return (List<User>)jdbcTemplate.query("select * from user", new RowMapper() { public Object mapRow(ResultSet rs, int arg) throws SQLException { User user = new User(); user.setUserID(rs.getInt("ID")); user.setUserName(rs.getString("NAME")); return user; } }); } public User getUser(int id) { try { return (User)jdbcTemplate.queryForObject("select * from user where id=?", new Object[]{id}, new int[]{java.sql.Types.INTEGER}, new RowMapper() { public Object mapRow(ResultSet rs, int arg) throws SQLException { User user = new User(); user.setUserID(rs.getInt("id")); user.setUserName(rs.getString("name")); return user; } }); } catch(Exception ex) { System.out.println(ex.getMessage()); } return null; } public void insertUser(User user) { jdbcTemplate.update("insert into user (id,name) values(?,?)", new Object[]{user.getUserID(), user.getUserName()}, new int[]{java.sql.Types.INTEGER, java.sql.Types.VARCHAR}); } public void updateUser(User user) { jdbcTemplate.update("update user set name=? where id=?", new Object[]{user.getUserName(), user.getUserID()}, new int[]{java.sql.Types.VARCHAR, java.sql.Types.INTEGER}); } }
JdbcTemplate还提供了一些其他的API,也非常实用。
接下来需要修改beans.xml:
<bean id="theDatasource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost/test" /> <property name="username" value="root" /> <property name="password" value="123" /> <property name="initialSize" value="2" /> <property name="maxActive" value="100" /> <property name="maxIdle" value="2" /> <property name="minIdle" value="1" /> </bean> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="theDatasource" /> </bean> <tx:annotation-driven transaction-manager="txManager" /> <bean id="userDao" class="sample.spring.jdbc.UserDaoImpl"> <property name="dataSource" ref="theDatasource"/> </bean>
这里theDataSource用来配置数据库连接信息;txManager来配置事务管理器的信息;userDao是我们的Dao实现类信息。
下面是测试方法:
public static void test1() { ApplicationContext ctx = new ClassPathXmlApplicationContext("sample/spring/jdbc/beans.xml"); UserDao userDao = (UserDao)ctx.getBean("userDao"); System.out.println("=====get all user====="); List<User> users = userDao.getAllUser(); for(User user:users) { System.out.println("ID:" + user.getUserID() + ";Name:" + user.getUserName()); } System.out.println("=====insert user====="); User user = new User(); user.setUserID(10); user.setUserName("Zhang Fei"); userDao.insertUser(user); user = userDao.getUser(10); System.out.println("ID:" + user.getUserID() + ";Name:" + user.getUserName()); System.out.println("=====update user====="); user.setUserName("Devil"); userDao.updateUser(user); user = userDao.getUser(10); System.out.println("ID:" + user.getUserID() + ";Name:" + user.getUserName()); System.out.println("=====delete user====="); userDao.deleteUser(user); user = userDao.getUser(10); if (user == null) { System.out.println("delete successfully."); } }
输出结果如下:
MySQL-AB JDBC Driver =====get all user===== ID:1;Name:Zhang San ID:2;Name:TEST =====insert user===== ID:10;Name:Zhang Fei =====update user===== ID:10;Name:Devil =====delete user===== Incorrect result size: expected 1, actual 0 delete successfully.
说到数据库事务,我们在上面的UserDaoImpl中可以看到,这个类的前面有一个名为@Transcational的Annotation声明,这是Spring实现事务的关键点,它既可以作用在类上,也可以作用在方法上。
@Transactional包含以下参数:
- propagation参数,Propagation类型(枚举),默认值为Propogation.REQUIRED,支持的值有REQUIRED、MANDATORY、NESTED、NEVER、NOT_SUPPORTED、REQUIRE_NEW、SUPPORTS。
- isolation参数,Isolation类型(枚举),默认值为Isolation.DEFAULT,支持的值有DEFAULT、READ_COMMITTED、READ_UNCOMMITTED、REPEATABLE_READ、SERIALIZABLE。
- timeout参数,int类型,事务的超时时间,默认值为-1,即不会超时。
- readOnly参数,boolean类型,true表示事务为只读,默认值为false。
- rollbackFor参数,Class<? extends Throwable>[]类型,默认为空数组。
- rollbackForClassName参数,String[]类型,默认为空数组。
- noRollbackFor参数,Class<? extends Throwable>[]类型,默认为空数组。
- noRollbackForClassName参数,String[]类型,默认为空数组。