除了我们常见的SSH框架整合,Spring还可以和JDBC整合,Spring容器提供专门针对JDBC操作的辅助类:JdbcTemplate,需要使用注入的方式给JDBC辅助类注入数据源。
事物的配置有两种,一种是通过注解的方式,另外一种是通过XML文件配置的方式。
@Transactional(类型=值)
1.事物超时设置:
@Transactional(timeout=30)//默认是30秒
2.事物隔离级别:
@Transactional(isolation=Isolation.READ_UNCOMMITTED)//读取未提交的数据(会出现脏读,不可重复读)基本不使用。
@Transactional(isolation=Isolation.READ_COMMITTED)读取已提交数据(出现不可重复读和幻读)。
@Transactional(isolation=Isolation.REPEATABLE_READ)可重复读(会出现幻读)。
@Transactional(isolation=Isolation.SERIALIZABLE)串行化。
注:
Mysql:默认为REPEATABLE_READ级别。
SQLSERVE:默认为READ_COMMITTED。
脏读:一个事物读取到另外一个事物未提交的更新数据。
不可重复读:在同一事物中,多次提取统一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一事物已提交的更新数据,相反,"可重复读"即在同一事物中多次读取数据时,能够保证所读数据一样,也就是说,后续读取不能读到另一事物已提交的更新数据。
幻读:一个事物读取到另一事物已提交的insert数据。
事物的传播行为:
1.@Transactional(propagation=Propagation.REQUIRED)如果有事物,那么加入事物,没有的话就新建一个(默认情况下)
2.@Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED):容器不为这个方法开启事物(这个非常实用,因为查询方法是不用开启事物的,我们可以声依永这个注解)。
3.@Transactional(propagation=Propagation.REQUIRES_NEW):不管是否存在事物,都创建一个新的事物,原来的挂起,新的执行完毕之后,继续执行老的事物。
4.@Transactional(propagation=Propagation.MANDATORY):必须在一个已有的事物中进行,否则抛出异常。
5.@Transactional(propagation=Propagation.NEVER):必须在一个没有的事物中执行,否则抛出异常(与Propagation.MANDATORY相反)。
6.@Transactional(propagation=Propagation.SUPPORTS):如果其他bean调用这个方法,在其他bean中声明事物,那就用事物,如果其他bean没有声明事物,那就不用事物。
下面我们通过一个Spring和JDBC整合的案例来深入探究!
项目结构:
数据访问层接口:
/**
* 数据操作层接口
* @author Liao
*
*/
public interface IUserDao {
/**
* 添加用户
* @param user
*/
public void add(User user);
/**
* 根据用户查找id
* @param id 用户id
* @return
*/
public User findById(int id);
/**
* 查找所有用户
* @return
*/
public List<User> findAllUser();
}
数据访问层接口实现类:
@Repository
public class UserDaoImpl implements IUserDao {
@Resource//注入jdbc操作模板
private JdbcTemplate jdbc;
@Override
public void add(User user) {
/*添加用户*/
jdbc.update("insert into t_user(uid,uname) values(?,?)", user.getUid(),user.getUname());
System.out.println("添加用户成功!");
}
@Override
public User findById(int id) {
return jdbc.queryForObject("select * from t_user where uid = ?", new Object[]{id}, new UserRowMapper());
}
@Override
public List<User> findAllUser() {
return jdbc.query("select * from t_user", new UserRowMapper());
}
}
注:在数据操作层接口实现类中注入由Spring提供的专门针对JDBC的辅助类。
业务逻辑层接口:
/**
* 业务逻辑层接口
* @author Liao
*
*/
public interface IUserService {
/**
* 添加用户
* @param user
*/
public void add(User user);
/**
* 根据用户查找id
* @param id 用户id
* @return
*/
public User findById(int id);
/**
* 查找所有用户
* @return
*/
public List<User> findAllUser();
}
业务逻辑层接口实现类:
@Service
@Transactional
public class UserServiceImpl implements IUserService {
@Resource//注入Dao对象
private IUserDao userDao;
@Override
public void add(User user) {
/*添加用户*/
userDao.add(user);
}
@Override
@Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
public User findById(int id) {
/*根据id查询用户*/
return userDao.findById(id);
}
@Override
@Transactional(readOnly=true,propagation=Propagation.NOT_SUPPORTED)
public List<User> findAllUser() {
/*查询所有用户 */
return userDao.findAllUser();
}
}
注:在业务接口实现类中配置事物,我们在类上面注解了@Transactional,表示当前类下所有的的方法都要开启事物,但是我们在findById()和findAllUser()这两个方法中又使用@Transactional,注意我们在方法上的注解表示不开启事物为只读。因为这两个方法是查询的,不需要开启事物,所以使用注解的方式非常灵活,可以随心所欲的配置。
控制层:
@Controller
public class UserAction {
@Resource//注入Service
private IUserService userService;
/**
* 添加用户
*/
public void add(){
User user = new User(3, "李克強");
/*通过业务逻辑对象进行添加*/
userService.add(user);
}
/**
* 根据id查询用户
* @param id
*/
public void findById(int id){
/*查询*/
User user = userService.findById(id);
System.out.println(user.getUname());
}
/**
* 查询所有用户
*/
public void findAllUser(){
List<User> users = userService.findAllUser();
System.out.println(users.size());
}
}
注:在UserAction中注入Service(通过注解的形式注入)。
获取bean的工具类:
/**
* 获取bean的工具类
* @author Liao
*
*/
public class BeanUtils {
private static AbstractApplicationContext app;
static {
app = new ClassPathXmlApplicationContext("beans.xml");
}
/**
* 获取bean
* @param name
* @param clazz
* @return
*/
public static <T> T getBean(String name,Class<T> clazz){
return app.getBean(name, clazz);
}
}
beans.xml文件中配置事物(通过注解)和各种参数:
<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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- 配置AOP切面 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<!-- 配置Spring自動掃面的包 -->
<context:component-scan base-package="com.lixue.dao.impl,com.lixue.service.impl,com.lixue.action" />
<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/spring_jdbc" />
<property name="username" value="root" />
<property name="password" value="134045" />
<!-- 连接池启动时的初始值 -->
<property name="initialSize" value="1" />
<!-- 连接池的最大值 -->
<property name="maxActive" value="500" />
<!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
<property name="maxIdle" value="2" />
<!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请一些连接,以避免洪峰来时再申请而造成的性能开销 -->
<property name="minIdle" value="1" />
</bean>
<!-- 配置spring提供的jdbc操作类,并通过构造方法的方式注入数据源 -->
<bean id="jdbc" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
<!-- 配置事物管理器,并注入數據源 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 采用@Transaction注解的方式使用事物 -->
<tx:annotation-driven transaction-manager="txManager" />
</beans>
注:上述配置是通过注解的方式来配置事物的。
<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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- 配置AOP切面 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<!-- 配置Spring自動掃面的包 -->
<context:component-scan base-package="com.lixue.dao.impl,com.lixue.service.impl,com.lixue.action" />
<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/spring_jdbc" />
<property name="username" value="root" />
<property name="password" value="134045" />
<!-- 连接池启动时的初始值 -->
<property name="initialSize" value="1" />
<!-- 连接池的最大值 -->
<property name="maxActive" value="500" />
<!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
<property name="maxIdle" value="2" />
<!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请一些连接,以避免洪峰来时再申请而造成的性能开销 -->
<property name="minIdle" value="1" />
</bean>
<!-- 配置spring提供的jdbc操作类,并注入数据源 -->
<bean id="jdbc" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
<!-- 配置事物管理器,并注入數據源 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 拦截器方式配置事物 -->
<tx:advice id="transactionAdvice" transaction-manager="txManager">
<tx:attributes>
<!--被拦截的类中以save开头的方法要使用事物-->
<tx:method name="save*" propagation="REQUIRED" />
<!--被拦截的类中以update开头的方法要使用事物-->
<tx:method name="update*" propagation="REQUIRED" />
<!--被拦截的类中以saveOrUpdate开头的方法要使用事物-->
<tx:method name="saveOrUpdate*" propagation="REQUIRED" />
<!--被拦截的类中以delete开头的方法要使用事物-->
<tx:method name="delete*" propagation="REQUIRED" />
<!--被拦截的类中以grant开头的方法要使用事物-->
<tx:method name="grant*" propagation="REQUIRED" />
<!--被拦截的类中以init开头的方法要使用事物-->
<tx:method name="init*" propagation="REQUIRED" />
<!--除了上面要配置方法,其他方法一律只读,不使用事物-->
<tx:method name="*" propagation="REQUIRED" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 通过aop切面配置要拦截的范围 -->
<aop:config>
<!-- 第一个*代表所有的返回值类型;第二个*代表所有的类;第三个*代表类所有方法;..代表子或者孙子包;最后一个..代表所有的参数 -->
<aop:pointcut id="transactionPointcut" expression="(execution(* com.lixue..*Impl.*(..)))" />
<aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice" />
</aop:config>
</beans>
测试类:
public class UserTest {
public static void main(String[] args) {
/*获取Action的bean实例*/
UserAction userAction = BeanUtils.getBean("userAction", UserAction.class);
/*添加用户*/
userAction.add();
/*根据id查询用户*/
//userAction.findById(1);
/*查询所有用户 */
userAction.findAllUser();
}
}
通过上面的XML形式也可以配置事物,这样配置以后,我们执行我们的测试方法,会发现有问题,如下图:
问题:Connection is read-only. Queries leading to data modification are not allowed
原因:出现这个问题的原因是我们在Service实现类中的添加方法的方法名是add,而我们配置事物时明确指定了添加方法必须以save或者saveOrUpdate开头。其他方法都没有开启事物,是只读的。
解决:解决方法也很简单那就是把我们Service实现类中的add方法名改为以save或者saveOrUpdate开头即可。