现在的位置: 首页 > 综合 > 正文

Spring与JDBC集合以及事物处理

2018年06月05日 ⁄ 综合 ⁄ 共 9002字 ⁄ 字号 评论关闭

除了我们常见的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>

注:上述配置是通过注解的方式来配置事物的。

除了通过注解的方式配置事物,Spring还提供使用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>

	 	<!-- 拦截器方式配置事物 -->
	<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开头即可。

【上篇】
【下篇】

抱歉!评论已关闭.