【j2ee spring】7、spring与数据库的连接的操作事务管理

来源:互联网 发布:网络上口口是什么意思 编辑:程序博客网 时间:2024/06/05 05:07

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);}@Overridepublic void save(Person person) {// TODO Auto-generated method stubjdbcTemplate.update("insert into person(name) values (?)", new Object[]{person.getName()}, new int[]{java.sql.Types.VARCHAR}); }@Overridepublic void update(Person person) {// TODO Auto-generated method stubjdbcTemplate.update("update person set name=? where id = ?", new Object[]{person.getName(), person.getId()}, new int[]{java.sql.Types.VARCHAR, java.sql.Types.INTEGER});}@Overridepublic Person getPerson(Integer personid) {// TODO Auto-generated method stubreturn (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());}@Overridepublic 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 批注,以使您不会再看到警告。虽然它听起来似乎会屏蔽潜在的错误,但实际上它将提高代码安全性,因为它将防止您对警告无动于衷 — 您看到的每一个警告都将值得注意。

 

http://zhidao.baidu.com/link?url=srtJ0lOakSMsQotStFzuI46L9ne5k3wUfDT_ediXFlf-H24dR0KVo99qQO1cyWG1oMfVwRVbFv2yWM_oE2-yP_


关键字  用途
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;@BeforeClasspublic static void setUpBeforeClass() throws Exception {try {ApplicationContext cxt = new ClassPathXmlApplicationContext("beans.xml");personService = (PersonService) cxt.getBean("personService");}catch (Exception e) {e.printStackTrace();}}@Testpublic void save(){for(int i = 0; i < 5; ++i){personService.save(new Person("xiaofeng"+i));}}@Testpublic void get(){Person person = personService.getPerson(7);System.out.println(person.getName());}@Testpublic void update(){Person person = personService.getPerson(7);person.setName("肖X");personService.update(person);}@Testpublic void delete(){personService.delete(10);}@Testpublic void getPersons(){for(Person person : personService.getPersons()){System.out.println(person.getName());}}@Testpublic 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.Driverurl=jdbc\:mysql\://localhost\:3306/cutter_point?useUnicode\=true&characterEncoding\=UTF-8username=rootpassword=xiaofeng2015initialSize=1maxActive=500maxIdle=2minIdle=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里面对这个操作的事务管理方式是成功的,就是把这个方法作为一个事务提交



0 0
原创粉丝点击