Spring + MyBatis 整合使用

集成 MyBatis

  • 使用 IoC 技术完成

    • IoC 创建对象,将 MyBatis 框架中的对象由 Spring 容器统一创建管理
    • 从 Spring 中获取对象,开发时只需要使用一个 Spring
  • MyBatis 使用步骤

    • 定义接口
    • 定义 Mapper 文件
    • 创建接口代理对象
  • MyBatis 获取代理对象

    // 读取配置文件,获取数据库连接信息和 mapper 文件映射信息
    Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
    // 通过配置文件创建 SqlSessionFactory 工厂对象
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
    // 通过工厂对象创建 SqlSession 对象
    SqlSession sqlSession = factory.openSession();
    // 通过 SelSession 对象得到 mapper 接口代理对象
    DemoMapper mapper = sqlSession.getMapper(DemoMapper.class);
    
  • 集成 Spring

    • 在配置文件中配置关键信息
      • 数据库据信息
        • MyBatis 使用的连接池功能较弱,使用其他连接池来代替
          • Druid 连接池
      • Mapper 文件映射
    • 通过配置文件得到 SqlSessionFactory 对象
      • 得到 SqlSeiison 对象再得到接口代理对象
      • xml 配置文件 <bean> 标签创建对象

操作流程

  1. 创建 Maven 项目
  2. 添加 Maven 依赖
    • Spring 依赖
    • MyBatis 依赖
    • MySQL 驱动
    • Spring 事务的依赖
    • MyBatis、Spring 集成的依赖
      • MyBatis 官方提供用来在 Spring 项目中创建 MyBatis 的 SqlsessionFactory、dao 对象
  3. 创建 MyBatis 实体类、接口 和 mapper 文件
  4. 创建 MyBatis 主配置文件、创建 Spring 主配置文件
    • 声明 MyBatis 对象,由 Spring 创建
      • 数据源
      • SqlSessionFactory 对象
      • 接口代理对象
      • 声明自定义 service
  5. 创建测试类
    • 获取 service 对象
    • 通过 service 调用接口代理对象完成数据库访问
Druid
  • Spring 配置文件中数据源 Druid 配置模板

    • 大多配置根据具体使用进行更改
    <!-- 配置 druid 数据源模板对象 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> 
         <property name="url" value="${jdbc_url}" />
         <property name="username" value="${jdbc_user}" />
         <property name="password" value="${jdbc_password}" />
    
         <property name="filters" value="stat" />
    
         <property name="maxActive" value="20" />
         <property name="initialSize" value="1" />
         <property name="maxWait" value="6000" />
         <property name="minIdle" value="1" />
    
         <property name="timeBetweenEvictionRunsMillis" value="60000" />
         <property name="minEvictableIdleTimeMillis" value="300000" />
    
         <property name="testWhileIdle" value="true" />
         <property name="testOnBorrow" value="false" />
         <property name="testOnReturn" value="false" />
    
         <property name="poolPreparedStatements" value="true" />
         <property name="maxOpenPreparedStatements" value="20" />
    
         <property name="asyncInit" value="true" />
     </bean>
    
SqlSessionFactory
  • Spring 配置文件中创建 SqlSessionFactory 对象

    <!-- 通过数据源对象创建 SqlSessionFactory 对象 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- set注入,将 druidDataSource 赋值给 dataSource -->
        <property name="dataSource" ref="druidDataSource"/>
        <!--
         读取 mybatis 主配置文件
         configLocation 是 Resource 类型,读取配置文件
         set 注入,使用 value 赋值:指定配置文件文件路径
        -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>
    
SqlSession
  • 可以得到 SqlSession 对象,但也要创建 dao 接口实现类
    • 在接口实现类中直接使用 sqlSession
    • 设置属性 sqlSession 并赋值直接使用 sqlsession
  • 或者直接在配置文件中生成接口代理对象
<!-- 得到 sqlsession 对象 -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
	<constructor-arg index="0" value="sqlSessionFactory"/>
</bean>
接口对象
声明 Dao 接口对象
  • 使用 MapperFactoryBean 创建 dao 接口代理对象
    • 单独创建接口代理对象
    • mapperInterface:接口路径
    • 赋值 SqlSessionFactroy 对象或 sqlsession对象
      • 同时赋值时 sqlSession 会覆盖 SqlSessionFactory
      • SqlSession 需要工厂创建,MapperFactoryBean 会接管工厂
单独注册
<!-- 单独注册 Mapper 接口代理对象 -->
<bean id="goodsMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    <property name="sqlSessionTemplate" ref="sqlSession"/>
    <property name="mapperInterface" value="demo.dao.GoodsDao"/>
</bean>
<!-- 一次只能声明一个 -->
<bean id="orderMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    <property name="sqlSessionTemplate" ref="sqlSession"/>
    <property name="mapperInterface" value="demo.dao.OrderDao"/>
</bean>
<!-- 声明 service 对象 -->
<bean id="service" class="demo.service.ShoppingServiceImpl">
    <property name="goods" ref="goodsMapper"/>
    <property name="order" ref="orderMapper"/>
</bean>
扫描注册
  • <mybatis:scan base-package="接口路径">

  • @MapperScan 注解扫描

    • 注解在配置类中使用,获取 接口代理对象
    @Configuration
    @MapperScan("org.mybatis.spring.sample.mapper")
    public class AppConfig {/* ... */}
    
  • MapperScannerCofigurer

    • 扫描接口所在包直接将所有接口创建代理对象并注册到 Spring 容器中
    • MapperScannerConfigurer 是一个 BeanDefinitionRegistryPostProcessor
      • <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
    • 需要指定 sqlSessionFactorysqlSessionTemplate
      • 应该要指定的是 bean 名 而不是 bean 的引用
      • 因此要使用 value 属性而不是通常的 ref 属性
<!--
创建接口对象使用 SqlSession 的 getMapper(Class<T> type) 方法;
MapperScannerConfigurer:在内部调用 getMapper() 方法生成每个dao接口代理对象
 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!-- 指定 SqlSessionFactory 对象 id,用来创建 SqlSession 对象 -->
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>

    <!-- 
    指定包名:dao 接口所在包的包名,多个包名用逗号分隔
    MapperScannerConfigurer扫描包中所有接口
        将每个接口都执行一次getMapper()方法,得到dao接口的代理对象,对象名默认是 dao 接口名首字母小写
    创建好的 dao 接口代理对象放到 Spring 容器中
    -->
    <property name="basePackage" value="demo.dao"/>
</bean>
声明 service 对象
  • 并为 Dao 接口对象属性赋值
<!-- 声明 service 对象 -->
<bean id="demoService" class="demo.service.DemoServiceImpl">
    <!-- set 注入赋值 -->
    <property name="demoDao" ref="demoDao"/>
</bean>
测试
  • Spring 整合 MyBatis 框架会默认自动提交事务
@Test
public void testAdd() {
    DemoService service = (DemoService) SpringUtil.getBean("demoService");
    int rows = service.add(new Demo(123, "二狗", 23, new Date()));
    System.out.println("受影响行数 " + rows);
}

@Test
public void testGetByName() {
    DemoService service = (DemoService) SpringUtil.getBean("demoService");
    List<Demo> all = service.getByName("狗");
    for (Demo demo : all) {
        System.out.println(demo);
    }
}

配置文件

Spring
applicationContext.xml
  • Spring 配置文件
    • 基本只需改动 MyBatis 配置文件的路径
      • Dao 接口包路径
    • 或自定义数据源配置
<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd 
	   http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 加载外部属性配置文件 -->
    <context:property-placeholder location="classpath:dataSource.properties"/>

    <!-- 声明数据源 dateSource,配置连接数据库需要的参数 -->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource" 
          init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <!-- 最大连接数 -->
        <property name="maxActive" value="20"/>
        <!-- 初始化创建连接数 -->
        <property name="initialSize" value="5"/>
        <!-- 最大等待时间 -->
        <property name="maxWait" value="6000"/>
    </bean>

    <!-- 创建 SqlSessionFactory 对象 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- set注入,将 druidDataSource 赋值给 dataSource -->
        <property name="dataSource" ref="druidDataSource"/>
        <!--
         读取 mybatis 主配置文件
         configLocation 是 Resource 类型,读取配置文件
         set 注入,使用 value 赋值:指定配置文件文件路径
        -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

    <!--
    创建接口对象,使用 SqlSession 的 getMapper(Class<T> type)
    MapperScannerConfigurer:在内部调用 getMapper() 方法生成每个dao接口代理对象
     -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 指定 SqlSessionFactory 对象 id,用来创建 SqlSession 对象 -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>

        <!--
        指定包名:dao 接口所在包的包名,多个包的包名用逗号分隔
        MapperScannerConfigurer扫描包中所有接口
            将每个接口都执行一次getMapper()方法,得到dao接口的代理对象
        创建好的 dao 接口代理对象放到 Spring 容器中
        -->
        <property name="basePackage" value="demo.dao"/>
    </bean>

    <!-- 声明 service 对象 -->
    <bean id="demoService" class="demo.service.DemoServiceImpl">
        <!-- set 注入赋值 -->
        <property name="demoDao" ref="demoDao"/>
    </bean>

</beans>
属性配置
jdbc.url=jdbc:mysql://localhost:3306/demo01
jdbc.username=root
jdbc.password=123456
MyBaits
  • 只需要指定 Mapper 文件映射
  • 不再使用 MyBatis 的 默认连接池
    • 即不再配置数据库连接环境
<?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>

	<settings>
         <!-- 打印操作日志到 控制台 -->
		<setting name="logImpl" value="STDOUT_LOGGING" />
	</settings>
    
    <!-- 数据源配置在 Spring 配置文件中,MyBatis 不再需要配置 -->
    
	<mappers>
		<!-- 指定 mapper 文件映射 -->
		<mapper resource="demo/dao/DemoDao.xml"/>
		<!-- 指定一个包中所有 mapper 文件映射 -->
	<!-- <package name="demo/dao"/>-->
	</mappers>

</configuration>

事务处理

  • 事务:一组 sql 语句的集合,作为一个整体执行
    • 同一个事物执行时同时成功或失败
  • 使用事务时机
    • 涉及多表多个 DML 语句执行需要保证语句同时成功或全部失败不执行操作以保证数据的一致
    • Java 中控制事务在 service 类的业务方法上
      • 业务方法调用多个 dao 接口方法执行 sql 语句
  • JDBC 及 MyBatis 访问数据库、处理事务方法
    1. JDBC:Connection con、con.commit()、con.rollback()
    2. MyBatis:SqlSession.commit()、SqlSession.rollback
    3. 不同数据库访问技术和处理事务的对象、方法不同
      • 要熟悉各种数据库事务处理逻辑、机制、对象、方法
  • Spring 有统一的事务处理模型
    • 使用统一的步骤、方式 完成不同数据库访问技术、事务处理机制
    • 抽象了事务处理的各方面。定义事务处理步骤
    • 声明式事务
      • 事务相关资源和内容都提供给 spring,由 spring 处理事务提交、回滚
      • 仅使用很少的代码量
使用
  • 固定步骤,提供事务使用信息给 spring
    1. spring 配置文件中使用 <bean> 声明数据库访问技术对应的事务管理器实现类
    2. 指定要加入事务功能的类、方法
    3. 指定方法需要的隔离级别、传播行为【超时 可不设置】
  • 事物内部提交、回滚事务使用事务管理器对象来代替完成
    • 事务管理器是一个接口和其众多实现类
      • 接口:PlatforinTranstionalManager
        • 定义事务方法:commit()rollback()
      • 实现类:spring 创建对应数据库访问技术的事务处理类
        • MyBatis:DataSourceTransactionManager
        • Hibernate:HibernateTransactionManager
控制事务
  • TransactionDefinition:事务定义接口
    • 定义三类常量及操作
隔离级别
  • 事务隔离级别:常量值,ISOLATION 开头

    /*
    采用 DB 默认事务隔离级别
    MySQL 默认 REPEATABLE_READ;Oracle 默认 READ_COMMITTED
    */
    int ISOLATION_DEFAULT = -1;
    // 读未提交,不解决任何问题
    int ISOLATION_READ_UNCOMMITTED = 1;
    // 读已提交,解决脏读,存在不可重复读和幻读
    int ISOLATION_READ_COMMITTED = 2;
    // 可重复读,解决不可重复读,有时可能会有幻读,MySQL 有优化
    int ISOLATION_REPEATABLE_READ = 4;
    // 串行化,不存在问题
    int ISOLATION_SERIALIZABLE = 8;
    
传播行为
  • 控制业务方法是否有事务,事务类型
    • 共 7 种传播行为,表示业务方法调用时事务如何使用
/*
* 指定的方法必须在事务内执行
* 若当前存在事务加入到当前事务;不存在事务则启动新事务
* 最常见使用,spring 默认的事务传播行为
*/
int PROPAGATION_REQUIRED = 0;

/*
* 指定的方法支持事务
* 当前存在事务则加入事务内执行,不存在事务也可以直接执行
*/
int PROPAGATION_SUPPORTS = 1;

/*
* 指定的方法必须在自己的事务中执行,一定启动新事物
* 若当前存在事务则将当前事务挂起,直到新事务执行完毕
* 新事物和原事务执行结果互不影响
*/
int PROPAGATION_REQUIRES_NEW = 3;
/*
* PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NOT_SUPPORTED
* 需要使用 JtaTransactionManager 作为事务管理器
*/

// 以下很少使用
int PROPAGATION_MANDATORY = 2;		// 表示该方法必须在事务中执行,若不存在事务则抛出异常
int PROPAGATION_NOT_SUPPORTED = 4;	// 表示方法不应在事务中执行,若存在事务该方法运行期间将当前事务挂起
int PROPAGATION_NEVER = 5;			// 表示方法不应在事务中执行,方法执行时若存在事务则抛出异常
/*
表示当前若已存在事务方法将在嵌套事务中执行,不存在则创建事务
外部事物回滚会影响嵌套事务回滚,嵌套事务不影响外部事物
嵌套事务可独立于当前事务进行单独提交或回滚,各厂商支持行为有所不同
*/
int PROPAGATION_NESTED = 6;
  • 例如:REQUIRED 传播行为在这里插入图片描述
默认超时时限
  • 方法最长执行时间:int TIMEOUT_DEFAULT = -1
    • 若方法执行超过时间进行事务回滚
    • 单位:s;整数值,默认 -1
  • 影响因素较多,一般不可控,一般不进行设置

例如:MyBatis

<!-- 声明事务管理器 -->
<bean id="mybatis" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/>
事务执行
  • 业务方法执行成功
    • spring 在方法执行后自动提交事务,由事务管理器 commit
  • 业务方法抛出运行时异常时
    • spring 执行回滚,调用事务管理器的 rollback
    • 运行时异常:RuntimeException 及其子类
  • 业务方法抛出非运行时异常
    • 提交事务
    • 非运行时异常:在编写代码时必须处理异常
使用事务

固定步骤,提供事务使用信息给 spring

  1. spring 配置文件 <bean> 声明数据库访问技术对应的事务管理器实现类
  2. 指定要加入事务功能的类、方法
  3. 指定方法需要的隔离级别、传播行为【超时 一般不设置】
  • Spring 框架提供的事务处理方案
注解方案
  • Spring 框架用 AOP 实现给业务方法增加事务的功能

    • 使用注解:@Transactional
      • 该注解由 Spring 框架提供
    • 注解在 public 方法上面,表示当前方法具有事务
    • 给注解属性赋值以设置事务隔离级别、传播行为、异常信息等
  • @Transactional 注解可选属性

    • propagation:设置事务传播属性
      • Propagation 是枚举类型,默认 Propagation.REQUIRED
    • isolation:设置事务隔离级别
      • Isolation 是枚举类型,默认 Isolation.DEFAULT
    • readOnly:设置方法对数据库是否是只读
      • boolean 类型,默认 false
    • timeout:设置超时时限,单位秒,类型 int;默认 -1:无时限
    • rollbackFor:指定需要回滚的异常类型
      • 类型 Class
      • 默认值是空数组,一个异常类无需使用数组
    • rollbackForClassName:指定需要回滚的异常类名
      • 类型 String[]
      • 默认值是空数组,一个异常类无需使用数组
    • noRollbackFor:指定不需要回滚的异常类
      • 类型 Class,默认值是空数组,一个异常类无需使用数组
    • noRollbackForClassName:指定不需要回滚的异常类类名
      • 类型 String[],默认值是空数组,一个异常类无需使用数组
  • 异常处理逻辑

    1. Spring 先检查异常是否在 rollbackFor 指异常类型中
      • 若存在,无论是什么异常类型必定回滚
    2. 不在其中,则判断异常是否是运行时异常
      • 若是则一定回滚
    3. 都不满足则进行自动提交
  • @Transactional 若用在方法上只能是 public 方法

    • 对于非 public 方法使用 Spring 不会报错,但不会将指定事务织入到该方法
      • Spring 会忽略所有非 public 方法上的 @Transactional 注解
    • 适合中小项目使用
  • @Transactional 使用步骤

    1. 声明事务管理器对象
    2. 开启事务注解驱动
      • <tx:annotation-driven transaction-manager="mybatis"/>
      • Spring 使用 AOP 机制,创建 @Transactional 所在类代理对象给方法加入事务功能
      • 业务方法执行之前开启事务,执行完成后提交事务
        • Spring 自动使用 AOP 的环绕通知
    3. 在业务方法上面添加注解
      • @Transactional
Aspectj
  • 使用 Aspectj 的 AOP 配置管理事务

    • 使用 xml 配置文件配置事务代理
      • 不足:每个目标类都需要配置事务代理
        • 目标类较多时配置文件臃肿
      • 但是可以自动为每个符合切入点表达式的类生成事务代理
    • 适合大型项目
      • 针对大量类、方法需要配置事务的情况
      • 使用 aspectj 框架在 spring 配置文件中声明类、方法需要的事务
      • 业务方法和事务配置完全分离
  • aspectj 使用步骤

    1. 添加 aspectj 的 maven 依赖
    2. 声明事务管理器对象
    3. 声明方法需要的事务类型
      • 配置方法的事务属性:隔离级别、传播行为、超时
        • name:完整方法名,不含包、类;使用 * 表示任意字符
        • rollbackFor:值是异常类全限定名,其他事务属性值正常配置
        • 基本使用默认属性值即可
    4. 配置 AOP
      • 指定要创建代理的包、类、方法,将事务织入
      • 关联配置的方法事务和切入点表达式
示例
<!--
使用 Spring 的事务处理,使用注解方式处理事务
开启事务注解驱动,使用注解管理事务,创建代理对象; 声明后添加 tx 约束文件
transaction-manager:事务管理器对象 id
-->
<tx:annotation-driven transaction-manager="mybatis"/>


<!--
声明式事务处理,和源代码完全分离,使用 aspectj 框架
id:自定义名,表示<tx:advice> 标签内的配置内容,标签来自 tx 约束文件
-->
<tx:advice id="myAdvice" transaction-manager="mybatis">
    <!-- 配置事务属性,查找时先匹配完全方法名,再匹配通配符 -->
    <tx:attributes>
        <!-- 给具体方法配置事务属性,一个标签配置一类方法 -->
         <!-- 配置方法的事务类型 -->
        <tx:method name="query*" read-only="true"/>
        <tx:method name="add*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
        <tx:method name="delete*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
        <tx:method name="update*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
        <tx:method name="*" read-only="true"/>			<!-- 覆盖所有方法设置只读 -->
    </tx:attributes>
</tx:advice>
<!-- 配置AOP进行事务织入 -->
<aop:config>
    <!--
    配置切入点表达式:指定要使用事务的包、类、方法
    id:切入点表达式唯一名
    expression:切入点表达式,指定使用事务的类,aspectj 创建代理对象
    -->
    <!-- service 包下所有类的的所有方法 -->
    <aop:pointcut id="servicePT" expression="execution(* *..service..*.*(..))"/>	
    <!-- 配置增强器,关联 advice 和 pointcut -->
    <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePT"/>
</aop:config>

完整使用

简单使用 Spring + MyBatis 框架的流程逻辑

  • IoC 使用
  • AOP 使用
  • 整合 MyBatis
  • 使用注解 或 配置文件 进行

配置文件

pom.xml
  • 添加相关需要的依赖信息
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>li_maven</groupId>
    <artifactId>demo12</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 配置项目编码、JDK版本属性 -->
    <properties>
        <project.build.encoding>UTF-8</project.build.encoding>
        <maven.compiler.source>16</maven.compiler.source>
        <maven.compiler.target>16</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- mysql 连接 Java 的驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>

        <!-- mybatis 依赖 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.9</version>
        </dependency>

        <!-- spring、mybatis 集成依赖 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.7</version>
        </dependency>

        <!-- spring 核心依赖,IoC -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.15</version>
        </dependency>

        <!-- spring 事务 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.3.15</version>
        </dependency>

        <!-- spring jdbc 依赖,进行事务支持 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.15</version>
        </dependency>

        <!-- druid 连接池依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>

        <!-- 使用 aspectj 进行 AOP 支持 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.16</version>
        </dependency>

        <!-- 单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <!-- 编译资源文件配置,将 properties、xml 配置文件都进行编译 -->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resource</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>
applicationContext.xml
  1. 配置数据源,自定义数据库连接池
  2. 声明 SqlSessionFactory 对象,读取 mybatis 配置文件
  3. 声明 dao 接口对象
  4. 声明事务管理器
  5. 使用 Spring 提供 AOP 实现进行注解添加事务
    • 开启事务注解驱动
    • 或使用 Aspectj 声明式事务处理
      1. 配置事务属性,根据不同方法设置
        • aspectj 使用 环绕通知处理
      2. 配置 AOP
        1. 配置切入点表达式:指定使用事务的包、类、方法
        2. 关联通知和切入点表达式
  6. 声明 service 对象
    • 给 service 对象中 dao 接口属性赋值
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 加载属性配置文件 -->
    <context:property-placeholder location="classpath:dataSource.properties"/>

    <!-- 声明数据源dateSource 连接数据库-->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource" 
          init-method="init" destroy-method="close">
        <!-- 使用属性配置文件引用赋值 -->
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <!-- 最大连接数 -->
        <property name="maxActive" value="20"/>
        <!-- 初始化创建连接数 -->
        <property name="initialSize" value="5"/>
        <!-- 最大等待时间 -->
        <property name="maxWait" value="6000"/>
    </bean>

    <!-- 创建 SqlSessionFactory 对象 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- set注入,将 druidDataSource 赋值给 dataSource -->
        <property name="dataSource" ref="druidDataSource"/>
        <!--
         读取 mybatis 主配置文件
         configLocation 是 Resource 类型,读取配置文件
         set 注入,使用 value 赋值:指定配置文件文件路径
        -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

    <!--
    创建 dao 接口对象,使用 SqlSession 的 getMapper(Class<T> type)
    无需指定 id,默认以接口名首字母小写
    MapperScannerConfigurer:在内部调用 getMapper() 方法生成每个dao接口代理对象
     -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 指定 SqlSessionFactory 对象 id,用来创建 SqlSession 对象 -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!--
        指定包名:dao 接口所在包的包名,多个包的包名用逗号分隔
        MapperScannerConfigurer扫描包中所有接口
            将每个接口都执行一次getMapper()方法,得到dao接口的代理对象
        创建好的 dao 接口代理对象放到 Spring 容器中
        -->
        <property name="basePackage" value="demo.dao"/>
    </bean>


    <!-- 声明事务管理器 -->
    <bean id="mybatis" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 配置属性声明数据源 -->
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

    <!--
    使用注解方式处理事务,使用 Spring 的事务处理
    开启事务注解驱动,使用注解管理事务,创建代理对象; 声明后添加 tx 约束文件
    transaction-manager:事务管理器对象 id
    -->
    <!-- <tx:annotation-driven transaction-manager="mybatis"/> -->


    <!--
    使用 aspectj 框架,声明式事务处理,和源代码完全分离
    id:自定义名,表示<tx:advice> 标签内的配置内容,标签来自 tx 约束文件
    -->
    <tx:advice id="myAdvice" transaction-manager="mybatis">
        <!-- 配置事务属性 -->
        <tx:attributes>
            <!-- 给具体方法配置事务属性,一个标签配置一类方法 -->
            <!--
            name:完整方法名,不含包、类;使用 * 表示任意字符
            rollbackFor:异常类全限定名;基本使用默认属性值即可
            -->
            <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"/>
            <!-- 通配符指定多个同类型方法,方法名定义时遵循规范 -->
            <tx:method name="query*" read-only="true"/>
            <tx:method name="*" read-only="true">
            <!-- 先匹配 buy,再查找query*,最后 * -->
        </tx:attributes>
    </tx:advice>
    <!-- 配置AOP -->
    <aop:config>
        <!--
        配置切入点表达式:指定要使用事务的包、类、方法
        id:切入点表达式唯一名
        expression:切入点表达式,指定使用事务的类,aspectj 创建代理对象
        -->
        <aop:pointcut id="servicePT" expression="execution(* *..service..*.*(..))"/>
        <!-- 配置增强器,关联 advice 和 pointcut -->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePT"/>
    </aop:config>

    <!-- 声明 service 对象 -->
    <bean id="shopping" class="demo.service.ShoppingServiceImpl">
        <!-- set 注入赋值 -->
        <property name="goods" ref="goodsDao"/>
        <property name="order" ref="orderDao"/>
    </bean>

</beans>
属性配置文件
  • 给数据源赋值的信息
jdbc.url=jdbc:mysql://localhost:3306/demo01
jdbc.username=root
jdbc.password=123456
mybatis-config.xml
  • 不再使用 MyBatis 的连接池
    • 所以不再配置数据源环境
  • 仅配置 mapper 文件的映射以及其他的 MyBatis 操作
<?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>

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <mappers>
        <mapper resource="demo/dao/GoodsDao.xml"/>
        <mapper resource="demo/dao/OrderDao.xml"/>
		<!-- <package name="demo/dao"/> -->
    </mappers>

</configuration>

domain

  • 实体类:简单Java类
    • 对应数据库的表
    • 字段和属性名相对应
Goods
package demo.domain;

public class Goods {
    private int id;
    private String name;
    private double price;
    private int nums;

    public Goods(){}

    public Goods( String name, double price, int nums) {
        this.name = name;
        this.price = price;
        this.nums = nums;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public long getNums() {
        return nums;
    }

    public void setNums(int nums) {
        this.nums = nums;
    }


    @Override
    public String toString() {
        return "商品: " +
                "\t编号 = " + id +
                ", \t商品名 = " + name +
                ", \t价格 = " + price +
                ", \t库存 = " + nums;
    }
}
Order
package demo.domain;

import java.text.SimpleDateFormat;
import java.util.Date;

public class Order {
    private int id;
    private int goodsId;
    private String goodsName;
    private int nums;
    private double totalPrice;
    private Date date;

    public Order() {
    }

    public Order(String goodsName, int goodsId, int nums, double totalPrice) {
        this.goodsName = goodsName;
        this.goodsId = goodsId;
        this.nums = nums;
        this.totalPrice = totalPrice;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getGoodsId() {
        return goodsId;
    }

    public void setGoodsId(int goodsId) {
        this.goodsId = goodsId;
    }

    public String getGoodsName() {
        return goodsName;
    }

    public void setGoodsName(String goodsName) {
        this.goodsName = goodsName;
    }

    public long getNums() {
        return nums;
    }

    public void setNums(int nums) {
        this.nums = nums;
    }

    public double getTotalPrice() {
        return totalPrice;
    }

    public void setTotalPrice(double totalPrice) {
        this.totalPrice = totalPrice;
    }


    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    private String date2String(Date date) {
        return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(date);
    }

    @Override
    public String toString() {
        return "订单: " +
                "\t编号 = " + id +
                ", \t商品编号" + goodsId +
                ", \t商品名 = " + goodsName +
                ", \t数量 = " + nums +
                ", \t总价 = " + totalPrice +
                ", \t日期 = " + date2String(date);
    }
}

dao

  • MyBatis 使用的 dao 接口和 mapper.xml 文件
GoodsDao.java
  • 商品数据层接口
package demo.dao;

import demo.domain.Goods;
import org.apache.ibatis.annotations.Param;
import java.util.List;

public interface GoodsDao {

    /**
     * 添加商品
     * @param goods 商品信息
     * @return 返回受影响行数
     */
    int add(Goods goods);

    /**
     * 修改商品信息
     * @param goods 商品信息
     * @return 返回受影响行数
     */
    int update(Goods goods);

    /**
     * 修改商品数量
     * @param id 根据id查找商品
     * @param nums 要修改的数量
     * @return
     */
    int updateNums(@Param("id") int id,
                   @Param("nums") int nums);

    /**
     * 查询商品清单
     * @return 返回当前所有商品
     */
    List<Goods> getAll();

    /**
     * 模糊根据商品名查询
     * @param name 商品名
     * @return 返回满足条件的记录
     */
    List<Goods> getByName(String name);

    /**
     * 根据 id 查询商品价格
     * @param id 主键 id
     * @return 返回查询到的商品的价格
     */
    double getPrice(int id);

    /**
     * 查询商品库存
     * @param id 主键 id
     * @return 返回商品库存
     */
    int getNums(int id);

    /**
     * 查询商品名
     * @param id 主键 id
     * @return 返回受影响行数
     */
    String getName(int id);
}
GoodsDao.xml
  • GoodsDao 的 mapper 文件
<?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">
<!--
    mybatis-3-mapper.dtd:约束文件名,拓展名 dtd
    约束文件作用:限制、检查当前文件中出现的标签、属性名必须符合 MyBatis 要求
-->

<!-- namespace:命名空间,自定义唯一值;使用 dao 接口全限定名 -->
<mapper namespace="demo.dao.GoodsDao">

    <resultMap id="goods" type="demo.domain.Goods">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="price" property="price"/>
        <result column="nums" property="nums"/>
    </resultMap>

    <insert id="add">
        insert into goods(name, price)
        values (#{name}, #{price})
    </insert>

    <update id="update">
        update goods set id = #{id}
        <if test="name != null">
            ,name = #{name}
        </if>
        <if test="price != 0">
            ,price = #{price}
        </if>
        <if test="nums != 0">
            ,nums = #{nums}
        </if>
        where id = #{id}
    </update>

    <update id="updateNums">
        update goods
        set nums = nums - #{nums}
        where id = #{id}
    </update>


    <select id="getAll" resultMap="goods">
        select *
        from goods
    </select>

    <select id="getByName" resultMap="goods">
        select name, price, nums
        from goods
        where name like '%' #{name} '%'
    </select>

    <select id="getPrice" resultType="double">
        select price
        from goods
        where id = #{id}
    </select>

    <select id="getNums" resultType="int">
        select nums
        from goods
        where id = #{id}
    </select>

    <select id="getName" resultType="String">
        select name
        from goods
        where id = #{id}
    </select>
</mapper>
OrderDao.java
package demo.dao;

import demo.domain.Order;
import java.util.List;

public interface OrderDao {

    /**
     * 添加订单记录
     * @param order
     * @return
     */
    int add(Order order);

    /**
     * 查询指定订单
     * @param id 主键id
     * @return 返回受影响行数
     */
    OrderDao getById(int id);

    /**
     * 查询所有订单
     * @return 返回订单记录
     */
    List<OrderDao> getAll();
}
OrderDao.xml
<?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">
<!--
    mybatis-3-mapper.dtd:约束文件名,拓展名 dtd
    约束文件作用:限制、检查当前文件中出现的标签、属性名必须符合 MyBatis 要求
-->

<!-- namespace:命名空间,自定义唯一值;使用 dao 接口全限定名 -->
<mapper namespace="demo.dao.OrderDao">

    <insert id="add">
        insert into `order`(goodsId, goodsName, nums, totalPrice, date)
        values (#{goodsId}, #{goodsName}, #{nums}, #{totalPrice}, #{date})
    </insert>

    <select id="getById" resultType="demo.domain.Order">
        select * from
            `order` where id = #{id}
    </select>

    <select id="getAll" resultType="demo.domain.Order">
        select * from `order`
    </select>

</mapper>

service

ShoppingService.java
  • 业务层接口
package demo.service;

import demo.domain.Goods;
import java.util.List;

public interface ShoppingService {
    void buy(int id, int nums);
    List<Goods> query();
}
ShoppingServiceImpl.java
  • 业务层实现类
    • 调用 dao 层的方法进行业务逻辑实现
package demo.service;

import demo.dao.GoodsDao;
import demo.dao.OrderDao;
import demo.domain.Goods;
import demo.domain.Order;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;

public class ShoppingServiceImpl implements ShoppingService {

    private GoodsDao goods;
    private OrderDao order;

    public void setGoods(GoodsDao goods) {
        this.goods = goods;
    }

    public void setOrder(OrderDao order) {
        this.order = order;
    }

//  @Transactional  //使用注解默认属性值,大多情况已经够用
    @Override
    public void buy(int id, int nums) {

        //判断商品库存
        int amount = goods.getNums(id);
        if (amount < nums) {
            System.out.println("商品库存不足,仅剩余 " + amount + "件");
            return;
        } else if (amount == 0) {
            System.out.println("商品库存已经清空");
            return;
        }

        //生成订单
        Order order = new Order();
        order.setGoodsId(id);
        order.setGoodsName(goods.getName(id));
        order.setNums(nums);
        order.setTotalPrice(nums * goods.getPrice(id));
        order.setDate(new Date());
        int rows = this.order.add(order);

        if (rows == 1){
            System.out.println("购买成功");
        }

        //修改商品库存
        rows = goods.updateNums(id, nums);
        if (rows == 1){
            System.out.println("已修改库存");
        }
    }

    /**
     * 查询所有商品
     */
    public List<Goods> query() {
        return goods.getAll();
    }
}

utils

  • 工具类
  • 封装一些简单操作
SpringUtil.java
  • 获取 bean 代理对象
  • 测试方法时使用
package demo.utils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringUtil {
    private static ApplicationContext context;
    static {
        // 读取 Spring 配置文件创建 Spring 容器对象
        context = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
    // 获取 bean 对象,实际仍是调用容器对象的 getBean 方法
    public static Object getBean(String id) {
        return context.getBean(id);
    }
}

test

testShopping.java
  • 测试 service 业务方法
package testDao;

import demo.domain.Goods;
import demo.service.ShoppingService;
import demo.utils.SpringUtil;
import org.junit.Test;

public class TestShopping {
    @Test
    public void testQuery() {
        ShoppingService shopping = (ShoppingService) SpringUtil.getBean("shopping");
        for (Goods goods : shopping.query()) {
            System.out.println(goods);
        }
    }

    @Test
    public void testBuy() {
        ShoppingService shopping = (ShoppingService) SpringUtil.getBean("shopping");
        shopping.buy(1, 20);
    }
}

Web项目

web 项目使用容器对象

  • Java 项目中使用 main 方法执行方法

    • 使用容器对象
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Object bean = context.getBean(id);
    
  • Web 项目运行在服务器

    • 服务器启动则项目一直运行
    • 需求
      • 在 Web 项目中容器对象只需要创建一次
      • 将容器对象放到全局作用域 ServletContext 中
  • 实现需求

    • 使用监听器
    • 当全局作用域对象被创建时创建容器对象,存入 ServletContext 中
  • 监听器作用

    • 创建容器对象
      • 执行 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    • 把容器对象放到 ServletContext 中
      • ServletContext.setAttribute(key, context)
  • 监听器可以自己创建

    • 或使用 Spring 框架中提供的 ContextLoaderListener
  • Web项目使用 Spring 基本流程

    1. 创建 Mavne Web 项目
    2. 添加依赖
      • 跟普通 Java 项目相同
      • 添加 jsp、servlet 依赖
      • 添加 spring-web 依赖:使用监听器对象
    3. 代码及配置文件与Java 项目类似
    4. 创建 jsp 发起请求:参数为 数据库表字段
    5. 创建 servlet 接收参数请求
      • 调用 service 业务方法,service 调用 dao 完成功能
    6. 创建 jsp 显示结果页面
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值