spring深入学习(五)-----spring dao、事务管理

访问数据库基本是所有java web项目必备的,不论是oracle、mysql,或者是nosql,肯定需要和数据库打交道。一开始学java的时候,肯定是以jdbc为基础,如下:

private static int insert(Student student) {
    String driver = "com.mysql.jdbc.Driver";
    String url = "jdbc:mysql://localhost:3306/samp_db";
    String username = "root";
    String password = "";
    Connection conn = null;
    int i = 0;
    String sql = "insert into students (Name,Sex,Age) values(?,?,?)";
    PreparedStatement pstmt;
    try {
        Class.forName(driver); //classLoader,加载对应驱动
        conn = (Connection) DriverManager.getConnection(url, username, password);
        pstmt = (PreparedStatement) conn.prepareStatement(sql);
        pstmt.setString(1, student.getName());
        pstmt.setString(2, student.getSex());
        pstmt.setString(3, student.getAge());
        i = pstmt.executeUpdate();
        pstmt.close();
        conn.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
 
    return i;
}

spring对dao层提供了不同的模板类,主要如下;

主要机制如下:

数据源

在spring中,数据连接是通过数据源获得的。数据源一般是由web应用服务器提供,spring中,通过jndi的方式获取,也可以直接在spring容器中配置,当然也可以通过代码的方式创建一个数据源。

1、dbcp数据源

jar包依赖:

commons-dbcp2-2.1.1.jar
commons-logging-1.2.jar
commons-pool2-2.4.2.jar

配置如下:

2、c3p0数据源

jar包依赖:

mchange-commons-java-0.2.11.jar
c3p0-0.9.5.2.jar

配置如下:

3、jndi数据源

如果应用配置在高性能的应用服务器上,那么可能希望使用应用服务器本身提供的数据源。应用服务器的数据源使用JNDI开放调用者使用,spring专门提供了引用jndi数据源的JndiObjectFactoryBean,配置如下:

另外spring为获取java ee资源提供了一个jee命名空间,通过jee可以有效的简化java ee资源的引用。利用jee命名空间引用jndi数据源的配置如下:

4、DriverManagerDataSource(spring提供)

 事务

针对事务,本人准备在后续数据库章节专门进行阐述,这里只要知道事务是为了保证一系列的操作要么同时成功,要么同时失败的机制。

jdbc事务操作:

在jdbc3.0之后,引入了savepoint的概念,流程如下;

spring对事务的支持

spring事务管理的亮点在于声明式事务管理,主要包括如下几个元素:

1、TransactionDefinition

定义了spring兼容的事务属性,包括事务隔离、事务传播、事务超时、只读状态等等。

2、TransactionStatus

代表一个事务的具体运行状态

3、PlatformTransactionManagerspring

提供的事务管理的对象

spring可以支持很多orm框架,例如mybatis、jpa、hibernate等,spring提供的事务管理类如下:

spring事务管理器实现类

1、spring jdbc、mybatis

基于数据源的connection访问数据库,所以可以使用DataSourceTransactionManager,配置如下:

DataSourceTransactionManager可以使用DataSource的connection对象的commit()、rollback()等方法来管理事务。

2、jpa

jpa通过javax.persistence.EntityTransaction来管理jpa事务。
其中EntityTransaction需要通过javax.persistence.EntityManager#getTransaction()获得,EntityManager则是由javax.persistence.EntityManagerFactory#createEntityManager()获取。

事务同步管理器

在spring中,jdbc的connection,hibernate的session等访问数据库的连接或会话对象统称为资源,这些资源在同一时刻是不能多线程共享的。那么如何解决呢?spring使用事务同步管理器(org.springframework.transaction.support.TransactionSynchronizationManager)使用ThreadLocal为不同事务线程提供了独立的资源副本,同时维护事务配置的属性和运行状态信息。

 

spring事务配置

1、xml配置方式

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd 
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
    http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
    <!-- 事务管理器 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 数据源 -->
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 通知 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 传播行为 -->
            <tx:method name="save*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>
    <!-- 切面 -->
    <aop:config>
        <aop:advisor advice-ref="txAdvice"
            pointcut="execution(* com.jeenotes.ssm.service.*.*(..))" />
    </aop:config>
</beans>

不过这种方式一看就不方便,下面说说注解方式

2、基于注解配置(@Transactional)

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd 
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
    http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
    <!-- 事务管理器 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 数据源 -->
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 开启事务控制的注解支持,配置 Annotation 驱动,扫描@Transactional注解的类定义事务。proxy-target-class为true代表使用CGLIB类代理;false则代表使用的是jdk动态代理 -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
</beans>
public class DefaultFooService implements FooService {
 
  public Foo getFoo(String fooName) {
    // do something
  }
 
  //方法上注解属性会覆盖类注解上的相同属性
  @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  public void updateFoo(Foo foo) {
    // do something
  }
}

@Transactional注解:

a、属性

b、作用范围

@Transactional注解可以应用于接口定义接口方法类定义类的public方法上。
但是不建议使用在接口中,主要是因为当proxy-target-class="true"时,接口的实现类将会工作在非事务环境下,所以spring建议在具体的类上使用@Transactional。

c、方法中使用@Transactional

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
 
  public Foo getFoo(String fooName) {
    // do something
  }
 
  // these settings have precedence for this method
  //方法上注解属性会覆盖类注解上的相同属性
  @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  public void updateFoo(Foo foo) {
    // do something
  }
}

spring事务易错点

 

1、关于spring事务,经常会有一种错误的说法,就是一个事务方法不应该调用另一个事务方法,否则将产生两个事务。

事务的传播行为

spring中的默认事务传播行为是PROPAGATION_REQUIRED,因此当一个方法使用@Transactional修饰,调用另一个使用@Transactional修饰的方法,则这两个方法其实是工作在同一个事务当中。

2、多线程下的事务安全

web容器基本都是多线程的,即使开发者认为自己没有开启多线程,但其实整个web容器为了能够处理更多的请求,都是使用共享线程池的方式来处理请求。在spring应用中,大部分的bean都是单例的,因此倒不会出现线程安全问题。

但是dao层持有的都是connection对象,这个连接对象肯定是状态化的。spring的解决方式就是通过ThreadLocal将有状态的变量(connection等)本地线程化,实现线程安全。

结论:在相同线程中进行相互嵌套调用的事务方法工作在相同的事务中;如果这些相互嵌套调用的方法工作在不同的线程中,则不同线程下的事务方法工作在独立的事务中。

3、哪些方法实现不了spring aop事务

大家都知道aop的实现原理主要有jdk动态代理或者cglib,那么对于spring aop事务的实现,还是有一定的要求的:

  • 基于jdk动态代理的aop事务基于接口,所以要求方法必须是public修饰,并且不能使用static修饰;
  • 而基于cglib字节码动态代理的方案是通过扩展被增强类,动态创建其子类的方式进行aop增强,但是final、static、private修饰的方法都不能被子类覆盖,所以这些方法无法实施aop增强。
原文地址:https://www.cnblogs.com/alimayun/p/10787529.html