8.Spring + JDBC + 事务(注解)





这次,讲述的是如题,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());

        }


    }

} 



那么,就来测试getPersongetPersons吧:










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值