Spring整合持久层开发

214 阅读4分钟

Spring — 整合持久层

第一章 持久层整合

1.Spring框架为什么要与持久层进行整合

1. JavaEE开发需要持久层进行数据库的访问操作
2. JDBC Hibernate Mybatis进行持久层开发过程存在大量的代码冗余
3. Spring基于模板设计模式对于上述的持久进行封装

2.Spring可以与哪些持久层技术进行整合

1. JDBC
	|-  JDBCTemplate
2. Hibernate (JPA)
	|-  HibernateTemplate
3. MyBatis
	|-  SqlSessionFactoryBean MapperScannerConfiguare

第二章 Spring与MyBatis整合

1.MyBatis开发步骤的回顾

1. 实体
2. 实体别名
3.4. 创建DAO接口
5. 实现Mapper文件
6. 注册Mapper文件
7. MyBatisAPI调用
  • 编码

    mybatis-config.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
        <typeAliases>
            <typeAlias type="com.hyy.mybatis.User" alias="user"></typeAlias>
        </typeAliases>
    
        <environments default="mysql">
            <environment id="mysql">
                <transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/testspring?serverTimezone=UTC"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                </dataSource>
            </environment>
        </environments>
    
        <mappers>
            <mapper resource="UserMapper.xml"></mapper>
        </mappers>
    
    </configuration>
    

    UserMapper

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.hyy.mybatis.UserDAO">
        <insert id="save" parameterType="user">
            INSERT INTO t_users(name,password) VALUES (#{name},#{password})
        </insert>
    </mapper>
    
    public class TestMybatis {
        public static void main(String[] args) throws IOException {
            InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            UserDAO userDao = sqlSession.getMapper(UserDAO.class);
    
            User user = new User(1,"迷雾小神","123456");
            userDao.save(user);
            sqlSession.commit();
        }
    }
    

2.Mybatis在开发过程中存在问题

配置繁琐,代码冗余

3.Spring与Mybatis整合思路分析

4.Spring与Mybatis整合的开发步骤

  • 配置文件 applicationContext.xml

    <!--创建SqlSessionFactory-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"></bean>
    
    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--指定实体类的包-->
        <property name="typeAliasesPackage" value="com.hyy.mybatis"/>
        <!--指定 映射文件的路劲,还有通配设置-->
        <property name="mapperLocations" value=""/>
    </bean>
    
    <!--DAO接口的实现类-->
    <bean id="configurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sessionFactory"></property>
        <property name="basePackage" value="com.hyy.mybatis"></property>
    </bean>
    
  • 编码

    实战经常根据需求,编写代码
    1. 实体
    2.3. 创建DAO接口
    4. 实现Mapper文件
    

5.Spring与MyBatis整合编码

  • 搭建开发环境

    <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.23</version>
    </dependency>
    
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.6</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>1.3.2</version>
    </dependency>
    
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.19</version>
    </dependency>
    
  • 配置文件

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/testspring?serverTimezone=UTC"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    
    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--指定实体类的包-->
        <property name="typeAliasesPackage" value="com.hyy.entity"/>
        <!--指定 映射文件的路劲,还有通配设置-->
        <property name="mapperLocations" >
            <list>
                <value>classpath:com.hyy.mapper/*mapper.xml</value>
            </list>
        </property>
    </bean>
    
    <!--DAO接口的实现类-->
    <bean id="configurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sessionFactory"></property>
        <property name="basePackage" value="com.hyy.dao"></property>
    </bean>
    
  • 编码

    1. 实体
    2.3. 创建DAO接口
    4. 实现Mapper文件
    

6.Spring与MyBatis整合细节

  • 问题:Spring与MyBatis整合后,为什么DAO不提交事务,但数据能插入到数据库中

    Connection ---> tx
    Mybatis(Connection)
    
    本质上控制连接对象(Connection) ---> 连接池(DataSource)
    1.MyBatis提供的连接对象 ---> 创建Connection
    	Connection.setAutoCommit(false)手工控制了事务,操作完成后,手工提交
    2.Druid作为连接池   ---> 创建Connection
    	Connection.setAutoCommit(true) 保持自动控制了事务
    

第三章 Spring的事务处理

1.什么是事务?

保证业务操作完整性的一种数据库机制

事务的特点:A C  I D
1.A 原子性
2.C 一致性
3.I 隔离性
4.D 持久性

2.如何控制事务

JDBC:
	Connection.setAutoCommit(false);
	Connection.commit();
	Connection.rollback();
Mybatis:
	Mybatis自动开启事务
	
	sqlSession.commit();
	sqlSession.rollback();
结论:控制事务的底层 都是Connection对象完成的。

3.Spring控制事务的开发

Spring是通过AOP方式进行事务开发
  • 原始对象

    public class XXXServiceImpl{
    	1. 原始对象 --> 原始方法 --> 核心功能
    	2. DAO作为Service的成员变量,依赖注入的方式进行赋值
    }
    
  • 额外功能

    org.springframework.jdbc.datasource.DataSourceTransactionManger
    注入Connection == 注入datasource
    
    1.MethodInterceptor
    	public Object invoke(MethodInvocation invocation){
    		try{
    			Connection.setAutoCommit(false);
    			Object ret = invocation.proceed();
    			Connection.commit();
    		}catch(Exception e){
    			Connection.rollback();
    		}
    		return ret
    	}
    2.@Aspect
      @Around
    
  • 切入点

    @Transactionnal
    事务的额外功能加入给哪些业务方法
    
    1.类上:类中所有的方法都会加入事务
    2.方法上:这个方法会加入事务
    
  • 组装切面

    1.切入点
    2.额外功能
    
    <tx:annontation-driven transaction-manager=""/>
    

4.Spring控制事务的编码

<bean id="userService" class="com.hyy.service.UserServiceImpl">
    <property name="userDAO" ref="userDAO"></property>
</bean>

<!--配置事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>
@Transactional
public class UserServiceImpl implements UserService {

    private UserDAO userDAO;

    public UserDAO getUserDAO() {
        return userDAO;
    }

    public void setUserDAO(UserDAO userDAO) {
        this.userDAO = userDAO;
    }

    public void register(User user) {
        userDAO.save(user);
    }
}

第四章 Spring中的事务属性(Transaction Attribute)

1.什么是事务属性

属性:描述物体特征的一系列值

事务属性:描述事务特征的一系列值
1.隔离属性
2.传播属性
3.只读属性
4.超时属性
5.异常属性

2.如何添加事务属性

@Transactional(isloation=,propagation=,readOnly=,timeout=,)

3.事务属性详解

1.1 隔离属性(ISLATION)

  • 隔离属性的概念

    概念:描述了事务解决并发问题的特征
    1.什么是并发
    	多个事务(用户)在同一时间,访问操作了相同的数据
    2.并发会产生哪些问题
    	脏读
    	不可重复读
    	幻读
    3.并发问题如何解决
    	通过事务的隔离属性解决,隔离属性中设置不同的值,解决并发处理过程中的问题
    
  • 事务并发会产生的问题

    • 脏读

      一个事务,读取了另一个事务中没有提交的数据。会在本事务中产生数据不一致的问题
      解决方案 @Transactional(isolation = Isolation.READ_COMMITTED) 读已提交
      
    • 不可重复读

      一个事务中,多次读取相同的数据,但是读取结果不一样。会在本事务中产生数据不一致的问题
      注意:1.不是脏读 2.一个事务中
      解决方案 @Transactional(isolation = Isolation.REPEATABLE_READ) 可重复读
      本质:加一把行锁
      
    • 幻读

      一个事务中,多次对整表进行查询统计,但是结果不一样。会在本事务中产生数据不一致的问题
      解决方案 @Transactional(isolation = Isolation.SERIALIZABLE) 序列化
      本质:表锁
      
    • 总结

      并发安全:SERIALIZABLE > REPEATABLE_READ > READ_COMMITTED
      运行效率:SERIALIZABLE < REPEATABLE_READ < READ_COMMITTED
      
  • 数据库对于隔离属性的支持

    隔离属性的值MysqlOracle
    READ_COMMITTED
    REPEATABLE_READ×
    SERIALIZABLE
    Oracle不支持REPEATABLE_READ的值 
    
  • 默认的隔离属性

    Isolation.DEFAULT:会调用不同数据所设置的默认隔离属性
    
    MySQL:REPEATABLE_READ
    Oracle:READ_COMMITTED
    
  • 隔离属性在实战中的建议

    推荐使用Spring指定的Isolation.DEFAULT
    

1.2 传播属性(PROPAGATION)

  • 传播属性的概念

    概念:描述了事务解决嵌套问题的特征
    事务嵌套:指的是一个大的事务中,包含了若干个小的事务
    
    问题:大事务中融入了很多小的事务,他们彼此影响,最终就会导致外部大的事务,丧失了事务的原子性
    
  • 传播属性的值及其方法

    传播属性的值外部不存在事务外部存在事务用法备注
    REQUIRED开启新的事务融合到外部事务中@Transactional(propagation = Propagation.REQUIRED)增、删、改方法
    SUPPORTS不开启事务融合到外部事务中@Transactional(propagation = Propagation.SUPPORTS)查询方法
    REQUIRES_NEW开启新的事务挂起外部事务,创建新的事务@Transactional(propagation = Propagation.REQUIRES_NEW)日志记录方法中
    NOT_SUPPORTED不开启事务挂起外部事务@Transactional(propagation = Propagation.NOT_SUPPORTED)极其不常用
    NEVER不开启事务抛出异常@Transactional(propagation = Propagation.NEVER)极其不常用
    MANDATORY抛出异常融合到外部事物中@Transactional(propagation = Propagation.MANDATORY)极其不常用
  • 默认的传播属性

    PROPAGATION_REQUIRED 是传播属性的默认值
    
  • 推荐传播属性的使用方法

    增、删、改方法:直接使用默认值REQUIRED
    查询  操作:显示指定传播属性的值为SUPPORTS
    

1.3 只读属性(readOnly)

针对于只进行查询操作的业务方法,可以加只读属性,提供运行效率

默认值:false

1.4 超时属性(timeout)

指定了事务等待的最长时间

1.为什么事务进行等待
   当前事务访问数据时,有可能访问的数据被别的事务进行加索的处理,那么本十五就必须进行等待
2.等待时间 秒

1.5 异常属性

Spring事务处理过程中
默认 对于RuntimeExeception及其子类 采用的是回滚的策略
默认 对于Exeception及其子类 采用的是提交的策略

rollbackFor = {java.lang.Exeception,xxx} 回滚
noRollbackFor = {java.lang.RuntimeExeception} 提交

4.事务属性常见配置总结

1.隔离属性 默认值
2.传播属性 Required(默认值)增删改、Supports 查询操作
3.只读属性 readOnly=false 增删改,true 查询操作
4.超时属性 默认值 -1
5.异常属性 默认值

增删改操作:@Transactional
查询操作:@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)

5.基于标签的事务配置方式(事务开发的第二种形式)

基于注解@Transactional事务的回顾
<bean id="userService" class="com.yusael.service.UserServiceImpl">
    <property name="userDAO" ref="userDAO"/>
</bean>

<!--DataSourceTransactionManager-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

@Transactional
public class UserServiceImpl implements UserService {
private UserDAO userDAO;

<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
基于标签的事务配置:
<bean id="userService" class="com.yusael.service.UserServiceImpl">
    <property name="userDAO" ref="userDAO"/>
</bean>

<!--DataSourceTransactionManager-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

事务属性
<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
    <tx:attributes>
        <tx:method name="register" isolation="DEFAULT" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<aop:config>
    <aop:pointcut id="pc" expression="execution(* com.yusael.service.UserServiceImpl.register(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>
  • 基于标签的事务配置在实战中的应用方式

    使用通配符的方式
    <tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
        <tx:attributes>
            <tx:method name="register"/>
            <tx:method name="modify*"/>
            编程时候, service中负责进行增删改操作的方法 都以 modify 开头
            						  查询操作 命名无所谓
            <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    
    <aop:config>
        <aop:pointcut id="pc" expression="execution(* com.yusael.service..*.*(..))"/>
       	应用的过程中, 将 service 都放到 service 包下
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
    </aop:config>