spring与数据库的连接的操作事务管理
1、首先我们的知道spring管理事务的方式有两种
还是一种是以注解的方式
在service类前加上@Transactional,声明这个service所有方法需要事务管理。每一个业务方法开始时都会打开一个事务。
Spring默认情况下会对运行期例外(RunTimeException)进行事务回滚。这个例外是unchecked
如果遇到checked意外就不回滚。
1 让checked例外也回滚:在整个方法前加上 @Transactional(rollbackFor=Exception.class)
2 让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)
3 不需要事务管理的(只查询的)方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)
在整个方法运行前就不会开启事务还可以加上:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true),这样就做成一个只读事务,可以提高效率。
各种属性的意义:
REQUIRED:
业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务。
NOT_SUPPORTED:
声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。
REQUIRESNEW:
不管是否存在事务,该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。
MANDATORY:
该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。
SUPPORTS:
该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。
NEVER:
该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常执行。
NESTED:
如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务 拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。
还有一种是基于xml文档的配置方式,等会我们会对他进行相应的说明和使用测试
2、编写程序来进行相应的xml文档配置的方式理解spring的事务管理
首先我们需要的一些类和文档,还有数据库,我这里使用的MySQL
结构图:
我们一个个地写:
Person.java
/**
* 功能:实现spring与jdbc的连接
* 时间:2015年3月26日21:09:20
* author:cutter_point
*/
package cn.cutter_point.bean;
public class Person
{
private Integer id;
private String name;
public Person() {} //默认构造函数
public Person(String name)
{
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
接口PersonService.java
/**
* 功能:实现spring与jdbc的连接
* 时间:2015年3月26日21:09:20
* author:cutter_point
*/
package cn.cutter_point.service;
import java.util.List;
import cn.cutter_point.bean.Person;
public interface PersonService
{
/**
* 保存person
* @param person
*/
public void save(Person person);
/**
* 更新person
* @param person
*/
public void update(Person person);
/**
* 根据id获取person
* @param personid
* @return
*/
public Person getPerson(Integer personid);
/**
* 获取所有的person
* @return
*/
public List<Person> getPersons();
/**
* 删除指定的person根据id号
* @param personid
*/
public void delete(Integer personid);
}
回调函数PersonRowMapper.java
/**
* 功能:实现spring与jdbc的连接
* 时间:2015年3月26日21:09:20
* author:cutter_point
*/
package cn.cutter_point.service.impl;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
import cn.cutter_point.bean.Person;
public class PersonRowMapper implements RowMapper
{
@Override
//这个类在调用的时候外面已经做了
//类似if(rs.next)的操作了,所以这里就不用这么做了
public Object mapRow(ResultSet rs, int index) throws SQLException
{
//这里面我们把查询到的结果返回
Person person = new Person(rs.getString("name"));
person.setId(rs.getInt("id"));
return person;
}
}
事务bean文件PersonServiceBean.java
/**
* 功能:实现spring与jdbc的连接
* 时间:2015年3月26日21:09:20
* author:cutter_point
*/
package cn.cutter_point.service.impl;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import cn.cutter_point.bean.Person;
import cn.cutter_point.service.PersonService;
public class PersonServiceBean implements PersonService
{
// private DataSource dataSource;
//这里我们使用spring里面的一个类容器
JdbcTemplate jdbcTemplate;
public PersonServiceBean() {}
/**
* 设置数据源
* @param dataSource
*/
public void setDataSource(DataSource dataSource)
{
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public void save(Person person)
{
// TODO Auto-generated method stub
jdbcTemplate.update("insert into person(name) values (?)", new Object[]{person.getName()}, new int[]{java.sql.Types.VARCHAR});
}
@Override
public void update(Person person)
{
// TODO Auto-generated method stub
jdbcTemplate.update("update person set name=? where id = ?", new Object[]{person.getName(), person.getId()},
new int[]{java.sql.Types.VARCHAR, java.sql.Types.INTEGER});
}
@Override
public Person getPerson(Integer personid)
{
// TODO Auto-generated method stub
return (Person) jdbcTemplate.queryForObject("select * from person where id = ?", new Object[]{personid},
new int[]{java.sql.Types.INTEGER}, new PersonRowMapper());
}
@Override
@SuppressWarnings("unchecked") //这个注解的作用是取消未检查的转换时的警告,吧这个警告取消掉
public List<Person> getPersons()
{
return (List<Person>)jdbcTemplate.query("select * from person", new PersonRowMapper());
}
@Override
public void delete(Integer personid)
{
jdbcTemplate.update("delete from person where id = ?", new Object[]{personid}, new int[]{java.sql.Types.INTEGER});
jdbcTemplate.update("delete from personxxx where id = ?", new Object[]{personid}, new int[]{java.sql.Types.INTEGER});
}
}
这里我说明一下里面的一个注解的作用:
@SuppressWarnings
J2SE 提供的最后一个批注是@SuppressWarnings。该批注的作用是给编译器一条指令,告诉它对被批注的代码元素内部的某些警告保持静默。
@SuppressWarnings 批注允许您选择性地取消特定代码段(即,类或方法)中的警告。其中的想法是当您看到警告时,您将调查它,如果您确定它不是问题,您就可以添加一个 @SuppressWarnings 批注,以使您不会再看到警告。虽然它听起来似乎会屏蔽潜在的错误,但实际上它将提高代码安全性,因为它将防止您对警告无动于衷 — 您看到的每一个警告都将值得注意。
关键字 用途
deprecation 使用了不赞成使用的类或方法时的警告
unchecked 执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型。
fallthrough 当 Switch 程序块直接通往下一种情况而没有 Break 时的警告。
path 在类路径、源文件路径等中有不存在的路径时的警告。
serial 当在可序列化的类上缺少 serialVersionUID 定义时的警告。
finally 任何 finally 子句不能正常完成时的警告。
all 关于以上所有情况的警告。
@SuppressWarnings 批注允许您选择性地取消特定代码段(即,类或方法)中的警告。其中的想法是当您看到警告时,您将调查它,如果您确定它不是问题,您就可以添加一个 @SuppressWarnings 批注,以使您不会再看到警告。虽然它听起来似乎会屏蔽潜在的错误,但实际上它将提高代码安全性,因为它将防止您对警告无动于衷 — 您看到的每一个警告都将值得注意。
下面是使用 @SuppressWarnings 来取消deprecation 警告的一个例子:
public class DeprecatedExample2 {
@Deprecated
public static void foo() {
}
}
public class DeprecatedUser2 {
@SuppressWarnings(value={"deprecation"})
public static void main(String[] args) {
DeprecatedExample2.foo();
}
}
@SuppressWarnings 批注接收一个 "value" 变量,该变量是一个字符串数组,它指示将取消的警告。合法字符串的集合随编译器而变化,但在 JDK 上,可以传递给 -Xlint 的是相同的关键字集合(非常方便)。并且要求编译器忽略任何它们不能识别的关键字,这在您使用一些不同的编译器时非常方便。
因为 @SuppressWarnings 批注仅接收一个参数,并为该参数使用了特殊的名称 "value",所以您可以选择省略 value=,作为一种方便的缩写:
public class DeprecatedUser2 {
@SuppressWarnings({"deprecation"})
public static void main(String[] args) {
DeprecatedExample2.foo();
}
}
您可以将单个数组参数中的任意数量的字符串值传递给批注,并在任何级别上放置批注。例如,以下示例代码指示将取消整个类的 deprecation 警告,而仅在 main() 方法代码内取消 unchecked 和 fallthrough 警告:
import java.util.*;
@SuppressWarnings({"deprecation"})
public class NonGenerics {
@SuppressWarnings({"unchecked","fallthrough"})
public static void main(String[] args) {
Runtime.runFinalizersOnExit();
List list = new ArrayList();
list.add("foo");
}
public static void foo() {
List list = new ArrayList();
list.add("foo");
}
}
@SuppressWarnings 是否比前两个批注更有用?绝对是这样。不过,在 JDK 1.5.0 版本中还没有完全支持该批注,如果您用 1.5.0 来尝试它,那么它将类似无操作指令。调用-Xlint:-deprecation 也没有任何效果。Sun 没有声明什么时候将增加支持,但它暗示这将在即将推出的一个 dot 版本中实现。
更进一步
如果您试图在 Javadocs 页面中查看这些属性,那么您可能很难找到它们。它们位于核心的 java.lang 包中,但有点隐蔽,它们出现在 Javadoc 类的最底端,列在 Exceptions 和 Errors 后面。
注意到了附加在 SuppressWarnings 批注后面的陌生的批注 @Target 和 @Retention 了吗?这些称为元数据批注,它们描述了该批注在哪里适用。我将在本系列的第二篇文章中介绍它们,以及介绍如何将元数据批注应用到您自己的批注中。
junit4的测试类
package junit.test;
import static org.junit.Assert.*;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.cutter_point.bean.Person;
import cn.cutter_point.service.PersonService;
public class PersonServiceTest
{
private static PersonService personService;
@BeforeClass
public static void setUpBeforeClass() throws Exception
{
try
{
ApplicationContext cxt = new ClassPathXmlApplicationContext("beans.xml");
personService = (PersonService) cxt.getBean("personService");
}
catch (Exception e)
{
e.printStackTrace();
}
}
@Test
public void save()
{
for(int i = 0; i < 5; ++i)
{
personService.save(new Person("xiaofeng"+i));
}
}
@Test
public void get()
{
Person person = personService.getPerson(7);
System.out.println(person.getName());
}
@Test
public void update()
{
Person person = personService.getPerson(7);
person.setName("肖X");
personService.update(person);
}
@Test
public void delete()
{
personService.delete(10);
}
@Test
public void getPersons()
{
for(Person person : personService.getPersons())
{
System.out.println(person.getName());
}
}
@Test
public void test()
{
// fail("Not yet implemented");
}
}
beans.xml的spring配置文件
<?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">
<!-- <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.gjt.mm.mysql.Driver" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" /> 两种都可以
<property name="url" value="jdbc:mysql://localhost:3306/cutter_point"/>?useUnicode=true&characterEncoding=UTF-8
<property name="username" value="root"/>
<property name="password" value="xiaofeng2015"/>
连接池启动时的初始值
<property name="initialSize" value="1" />
连接池的最大值
<property name="maxActive" value="500"/>
最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止
<property name="maxIdle" value="2"/>
最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请
<property name="minIdle" value="1"/>
</bean> -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${driverClassName}" />
<property name="url" value="${url}"/><!-- ?useUnicode=true&characterEncoding=UTF-8 -->
<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>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- <tx:annotation-driven transaction-manager="txManager"/> -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" propagation="NOT_SUPPORTED"/>
<!-- REQUIRESNEW:不管是否存在事务,该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。 -->
<tx:method name="*"/><!-- 只对前提是get的方法进行相应的处理,其余的默认 -->
</tx:attributes>
</tx:advice>
<aop:config>
<!-- aop切入点,expression表示 在cn.cutter_point.service包下包含子包的所有类的所有方法,不论有没有参数,几个参数都要进行拦截 -->
<aop:pointcut expression="execution(* cn.cutter_point.service..*.*(..))" id="transactionPointcut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPointcut"/><!-- 确定对切入点的操作和切入点 -->
</aop:config>
<bean id="personService" class="cn.cutter_point.service.impl.PersonServiceBean" >
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
然后就是映射文件,xml中用${}占位符的地方的值(尼玛,这一不小心不是吧自己数据库的密码暴露了吗!!!)
driverClassName=org.gjt.mm.mysql.Driver
url=jdbc\:mysql\://localhost\:3306/cutter_point?useUnicode\=true&characterEncoding\=UTF-8
username=root
password=xiaofeng2015
initialSize=1
maxActive=500
maxIdle=2
minIdle=1
最后就是数据库的设置了MySQL
create database cutter_point;
create table `cutter_point`.`person`
(
`id` int not null auto_increment,
`name` varchar(20) not null,
primary key(`id`)
)ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
数据库中的一些测试数据,我是调用前面测试类里面的函数加入的数据
好的,接下来我们来说明一下spring对事物的管理
这个是我们对事务的管理,但是我们怎么知道道理有没有作用呢???
这里我们看看我们的delete函数,我们把PersonServiceBean类里面的delete函数改变一下
public void delete(Integer personid)
{
jdbcTemplate.update("delete from person where id = ?", new Object[]{personid}, new int[]{java.sql.Types.INTEGER});
jdbcTemplate.update("delete from personxxx where id = ?", new Object[]{personid}, new int[]{java.sql.Types.INTEGER});
}
如果这两个操作是在一个事务中的话,那么下面那个personxxx是不存在的肯定会报错,那么删除的话事务回滚的话两个都会回滚,那么上面那条也不能执行!!!
我们测试一下,执行PersonServiceTest中的
public void delete()
{
personService.delete(10);
}
执行我们看看是不是会吧10号删除掉??
ok程序报错,说明第二句肯定是无法执行,是错的,但是我们看看
jdbcTemplate.update("delete from person where id = ?", new Object[]{personid}, new int[]{java.sql.Types.INTEGER});
jdbcTemplate.update("delete from personxxx where id = ?", new Object[]{personid}, new int[]{java.sql.Types.INTEGER});
这两句是在一个事务里面,还是两个分别建立了自己的事务呢?
我们看看数据库!!
依旧纯在,说明两个语句公用一个事务,接下来我们把beans.xml里面的配置注解掉
我们再次执行
执行PersonServiceTest中的
public void delete()
{
personService.delete(10);
}
依旧出错,
最后我们看看数据库里面的数据
数据消失,说明spring里面对这个操作的事务管理方式是成功的,就是把这个方法作为一个事务提交