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

结构图:

spring 数据库读写分离框架 spring数据库操作_事务

我们一个个地写:

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 了吗?这些称为元数据批注,它们描述了该批注在哪里适用。我将在本系列的第二篇文章中介绍它们,以及介绍如何将元数据批注应用到您自己的批注中。

 

http://zhidao.baidu.com/link?url=eB0Hyrhazjmnw4INMYlVF4A93hFK42ctoTrdzV5OrFSiJBJceIQlxWvNycq6ESEy8XZFgsyU_9Vd06R0hEHNSK



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 数据库读写分离框架 spring数据库操作_数据库_02


好的,接下来我们来说明一下spring对事物的管理

spring 数据库读写分离框架 spring数据库操作_数据库_03


这个是我们对事务的管理,但是我们怎么知道道理有没有作用呢???

这里我们看看我们的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);
}


spring 数据库读写分离框架 spring数据库操作_事务_04



执行我们看看是不是会吧10号删除掉??

spring 数据库读写分离框架 spring数据库操作_spring_05


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});

这两句是在一个事务里面,还是两个分别建立了自己的事务呢?


我们看看数据库!!

spring 数据库读写分离框架 spring数据库操作_spring_06

依旧纯在,说明两个语句公用一个事务,接下来我们把beans.xml里面的配置注解掉

spring 数据库读写分离框架 spring数据库操作_spring_07

我们再次执行

执行PersonServiceTest中的

public void delete()
{
	personService.delete(10);
}

依旧出错,

spring 数据库读写分离框架 spring数据库操作_spring 数据库读写分离框架_08



最后我们看看数据库里面的数据

spring 数据库读写分离框架 spring数据库操作_事务_09


数据消失,说明spring里面对这个操作的事务管理方式是成功的,就是把这个方法作为一个事务提交