【j2ee spring】7、spring与数据库的连接的操作事务管理
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 批注,以使您不会再看到警告。虽然它听起来似乎会屏蔽潜在的错误,但实际上它将提高代码安全性,因为它将防止您对警告无动于衷 — 您看到的每一个警告都将值得注意。
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 了吗?这些称为元数据批注,它们描述了该批注在哪里适用。我将在本系列的第二篇文章中介绍它们,以及介绍如何将元数据批注应用到您自己的批注中。
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里面对这个操作的事务管理方式是成功的,就是把这个方法作为一个事务提交
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。