这次,讲述的是如题,Spring+JDBC+事务(注解)。因为涉及到数据库的操作,所以顺道
把事务注解一起述说了,因为很简单。
数据源的配置、配置事务管理器(Spring中配置事务bean)和开启事务注解:
beans_4.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: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-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/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<!-- classpath:jdbc.properties表示类路径(src包)下的jdbc.properties -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="${dataSource}" destroy-method="close">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<!-- 连接池启动时的初始值 -->
<property name="initialSize" value="${initialSize}"/>
<!-- 连接池的最大值 -->
<property name="maxActive" value="${maxActive}"/>
<!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分
一直减少到maxIdle为止 -->
<property name="maxIdle" value="${maxIdle}"/>
<!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
<property name="minIdle" value="${minIdle}"/>
</bean>
<!-- 扫描com.zyy下所有子包的类 -->
<context:component-scan base-package="com.zyy"></context:component-scan>
<!-- 配置事务bean -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
数据源的配置就是按照那个配置格式修改就可以了。
配置事务bean利用到了spring对jdbc的事务支持。
开启事务注解就一句话:<tx:annotation-driven transaction-manager="txManager"/>
xml中的value值采用了资源文件的形式存储在jdbc.properties:
dataSource = org.apache.commons.dbcp.BasicDataSource
driverClassName = org.gjt.mm.mysql.Driver
url = jdbc:mysql://localhost:3306/ceshi_1?useUnicode=true&characterEncoding=UTF-8
username = root
password = root
initialSize = 1
maxActive = 500
maxIdle = 2
minIdle = 1
配置一个bean,来执行事务和数据库操作。PersonServiceBean_6:
package com.zyy.service.impl;
import com.zyy.dao.Person;
import com.zyy.service.PersonService_6;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.sql.Types;
import java.util.List;
/**
* Created by CaMnter on 2014/8/22.
*/
/**
* @Transactional 事务注解 可以表示 这个bean下执行每一个方法的前都会打开失误 执行后都会关闭失误
*
* 假如一个方法有多条sql语句 那么都会在一个事务内 ,不加注解则在不同的事务中执行
* @Transactional(noRollbackFor = Exception.class) Exception异常不回滚 这样即使有异常也执行Sql
* @Transactional(rollbackFor = Exception.class) Exception异常回滚 有Exception异常 不执行
*
*
* 事务如果有异常 不处理 都会回滚 不执行sql
* 如果有异常 处理了 不回滚 继续执行 这两点可以利用以上的特性修改
* @Transactional(propagation=Propagation.NOT_SUPPORTED) 表示不经过事务管理 被标注的方法不会开启事务
*
* 事务的传播属性
* (80%使用)REQUIRED:业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务
* 否则为自己创建一个新的事务。
*
* NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事
* 务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行。
*
* REQUIRESNEW:属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中
* 则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复
* 执行。
*
* MANDATORY:该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在
* 没有事务的环境下调用,容器就会抛出例外。
*
* SUPPORTS:这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在
* 事务范围外被调用,则方法在没有事务的环境下执行。
*
* Never:指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出例外,只有业务方法没
* 有关联到任何事务,才能正常执行。
*
* NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按REQUIRED属性执行.它使用了
* 一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对
* DataSourceTransactionManager事务管理器起效
*
*
* 如果一个类标注了事务@Transactional
*
* 那么它的每一个方法都是默认标注了 @Transactional(propagation = Propagation.REQUIRED)
* @Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly = true) 只读事务 一般读取数据的时候用
* 可以提高性能
*
*
*
* 数据库系统提供了四种事务隔离级别供用户选择。不同的隔离级别采用不同的锁类型来实现,在四种隔离级别中
* Serializable的隔离级别最高,Read Uncommited的隔离级别最低。大多数据库默认的隔离级别为
* Read Commited,如SqlServer,当然也有少部分数据库默认的隔离级别为Repeatable Read ,如Mysql
*
* (基本不用) Read Uncommited:读未提交数据(会出现脏读,不可重复读和幻读)。
*
* Read Commited:读已提交数据(会出现不可重复读和幻读)
*
* (一般使用) Repeatable Read:可重复读(会出现幻读)
*
* Serializable:串行化
*
*
* 脏读:一个事务读取到另一事务未提交的更新新据。
*
* 不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一事务
* 已提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是,后续读取
* 不能读到另一事务已提交的更新数据。
*
*
* 幻读:一个事务读取到另一事务已提交的insert数据。
*
*
* Mysql 默认的是 :Repeatable Read 可重复读(会出现幻读)
*
* 级别越高对于并发性能影响越大
*/
@Service("personServiceBean_6")
@Transactional
public class PersonServiceBean_6 implements PersonService_6 {
/**
* Spring 提供的 org.springframework.jdbc.core.JdbcTemplate
* 用语操作数据库
*/
private JdbcTemplate jdbcTemplate;
@Autowired()
private DataSource dataSource;
/**
* 实例化personServiceBean_6 前执行的方法
*
* 用于实例化JdbcTemplate 对象进行数据库操作
*/
@PostConstruct
public void initJdbcTemplate() {
//System.out.println(dataSource);
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public void save(Person peroson) {
/**
* 第一参数:sql语句
* 第二参数:通配符(?)的值
* 第三参数:通配符(?)的值的类型
* java.sql.Types中取得
*/
this.jdbcTemplate.update("insert into person(name) values(?)",
new Object[]{peroson.getName()},
new int[]{Types.VARCHAR});
}
public void update(Person peroson) {
/**
* 第一参数:sql语句
* 第二参数:通配符(?)的值
* 第三参数:通配符(?)的值的类型
* java.sql.Types中取得
*/
this.jdbcTemplate.update("update person set name = ? where id = ?",
new Object[]{peroson.getName(), peroson.getId()},
new int[]{Types.VARCHAR, Types.INTEGER});
}
public void delete(Integer personId) {
/**
* 第一参数:sql语句
* 第二参数:通配符(?)的值
* 第三参数:通配符(?)的值的类型
* java.sql.Types中取得
*/
this.jdbcTemplate.update("delete from person where id = ?",
new Object[]{personId},
new int[]{Types.INTEGER});
}
/**
* 查询数据不需要事务,所有配成了propagation = Propagation.NOT_SUPPORTED
*
* 和readOnly = true
*
* 只读事务
*
* @param personId
* @return
*/
@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
public Person getPerson(Integer personId) {
/**
* 第一参数:sql语句
* 第二参数:通配符(?)的值
* 第三参数:通配符(?)的值的类型
* java.sql.Types中取得
* 第四参数:RowMapper类型
* (这里需要设计一个RowMapper的子类用于返回数据)
*/
return (Person) this.jdbcTemplate.queryForObject("select * from person where id = ?",
new Object[]{personId},
new int[]{Types.INTEGER},
new PersonRowMapper());
}
@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
public List<Person> getPersons() {
/**
* 第一参数:sql语句
* 第二参数:通配符(?)的值
* 第三参数:通配符(?)的值的类型
* java.sql.Types中取得
* 第四参数:RowMapper类型
* (这里需要设计一个RowMapper的子类用于返回数据)
*/
return (List<Person>) this.jdbcTemplate.query("select * from person", new PersonRowMapper());
}
}
需要的RowMapper的子类用于返回数据,PersonRowMapper:
package com.zyy.service.impl;
import com.zyy.dao.Person;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 外部已经做过了 if(resultSet.next())
*/
public class PersonRowMapper implements RowMapper {
public Object mapRow(ResultSet resultSet, int i) throws SQLException {
Person person = new Person(resultSet.getString("name")) ;
person.setId(resultSet.getInt("id"));
return person;
}
}
配置事务很简单,只需要在类名上添加注解:@Transactional。
1.可以表示 这个bean下执行每一个方法的前都会打开失误 执行后都会关闭失误。
2.假如一个方法有多条sql语句 那么都会在一个事务内 ,不加注解则在不同的事务中执行。
3.@Transactional(noRollbackFor = Exception.class) Exception异常不回
滚 这样即使有异常也执行Sql
@Transactional(rollbackFor = Exception.class) Exception异常回滚
有Exception异常 不执行
4.事务如果有异常 不处理 都会回滚 不执行sql。
5.如果有异常,处理了,不回滚,继续执行 这两点可以利用以上的特性修改然后使其回滚。
( @Transactional(rollbackFor = Exception.class) )
接下来,介绍一下 事务的传播属性:
REQUIRED(80%使用率):业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中
那么加入到该事务,否则为自己创建一个新的事务。
NOT_SUPPORTED(查询操作可以加):声明方法不需要事务。如果方法没有关联到一个事务,容器不会为
它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方
法调用结束后,原先的事务便会恢复执行。
REQUIRESNEW:属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。
如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被
创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行。
MANDATORY:该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起
自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出例外。
SUPPORTS:这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事
务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下
执行。
Never:指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会
抛出例外,只有业务方法没有关联到任何事务,才能正常执行。
NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按
REQUIRED属性执行.它使用了一个单独的事务,这个事务拥有多个可以回滚的保存
点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager
事务管理器起效
然后在了解一下,数据库系统提供了四种事务隔离级:
数据库系统提供了四种事务隔离级别供用户选择。不同的隔离级别采用不同的锁类型来实现
在四种隔离级别中,Serializable的隔离级别最高,Read Uncommited的隔离级别最
低。大多数据库默认的隔离级别为Read Commited,如SqlServer,当然也有少部分数
据库默认的隔离级别为Repeatable Read ,如Mysql
ReadUncommited:读未提交数据(会出现脏读,不可重复读和幻读)。
ReadCommited:读已提交数据(会出现不可重复读和幻读)
RepeatableRead:可重复读(会出现幻读)
Serializable:串行化
脏读:一个事务读取到另一事务未提交的更新新据。
不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取
可以读到另一事务已提交的更新数据。相反,“可重复读”在同一事务中多次读取数
据时,能够保证所读数据一样,也就是,后续读取不能读到另一事务已提交的更新数
据。
幻读:一个事务读取到另一事务已提交的insert数据。
junit4.4测试代码:
package test.junit.test;
import com.zyy.dao.Person;
import com.zyy.service.PersonService_6;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* SpringJDBCTest Tester.
*
* @author <Authors name>
* @version 1.0
*/
public class SpringJDBCTestTest {
private PersonService_6 personService_6 ;
@Before
public void before() throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans_4.xml");
this.personService_6 = (PersonService_6)applicationContext.getBean("personServiceBean_6");
}
@After
public void after() throws Exception {
}
@Test
public void save() {
personService_6.save(new Person("CaMnter"));
}
@Test
public void update() {
Person person = new Person("CaMnter_update") ;
person.setId(2);
personService_6.update(person);
}
@Test
public void delete() {
personService_6.delete(3);
}
@Test
public void getPerson() {
Person person = personService_6.getPerson(new Integer(2)) ;
System.out.println("id:"+person.getId()+" name:"+person.getName());
}
@Test
public void getPersons() {
for (Person person : this.personService_6.getPersons()) {
System.out.println("id:"+person.getId()+" name:"+person.getName());
}
}
}
那么,就来测试getPerson和getPersons吧: