Spring 03 Spring 整合 MyBatis

10 篇文章 0 订阅
4 篇文章 0 订阅

Spring 整合 MyBatis

一、目标与回顾

1.1.本章目标
  1. 掌握使用Spring框架整合MyBatis
  2. 掌握使用映射器实现整合
  3. 掌握Spring声明式事务
1.2.Maven依赖包
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         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>com.aiden</groupId>
    <artifactId>T133-Spring</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <!--父级项目公用依赖包-->
    <dependencies>
        <!--单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <!--日志依赖-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!--用于简化实体类的编写-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>
        <!--spring-webmvc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>
        <!--spring-jdbc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>
        <!--支持切入点表达式等等-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>
        <!--mybatis-spring整合包【重要】-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.6</version>
        </dependency>
        <!--mysql驱动依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
    </dependencies>
</project>
1.3.MyBatis开发环境搭建

使用MyBatis的开发步骤

步骤1、在maven中添加Mybatis依赖包
步骤2、编写MyBatis核心配置文件(configuration.xml)
步骤3、创建实体类-POJO
步骤4、DAO(mapper)层-SQL映射文件(mapper.xml)
步骤5、创建测试类
​ ① 读取核心配置文件mybatis-config.xml
​ ② 创建SqlSessionFactory对象,读取配置文件
​ ③ 创建SqlSession对象
​ ④ 调用mapper文件进行数据操作

<!--步骤1、在maven中添加Mybatis依赖包-->
<dependency>
 <groupId>org.mybatis</groupId>
 <artifactId>mybatis</artifactId>
 <version>3.5.6</version>
</dependency>
<!--步骤2、编写MyBatis核心配置文件(configuration.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>
 <!--引入配置文件-->
 <properties resource="database.properties"></properties>
 <!--配置日志输出-->
 <settings>
     <setting name="logImpl" value="STDOUT_LOGGING"/>
 </settings>
 <!--配置类别名-->
 <typeAliases>
     <package name="com.cvs.pojo"/>
 </typeAliases>
 <!--环境配置 default默认-->
 <environments default="development">
     <!--可配置多个开发环境:本地环境、线上测试环境、灰度环境、生产环境-->
     <!--开发环境配置-->
     <environment id="development">
         <!--事务管理:JDBC-->
         <transactionManager type="JDBC"/>
         <!--配置数据源-->
         <dataSource type="POOLED">
             <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
             <property name="url"
                       value="jdbc:mysql://localhost:3306/cvs_db?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;Asia/Shanghai"/>
             <property name="username" value="root"/>
             <property name="password" value="root"/>
         </dataSource>
     </environment>

     <!-- 测试环境-->
     <environment id="test">
         <transactionManager type="JDBC"/>
         <dataSource type="POOLED">
             <property name="driver" value="${driver}"/>
             <property name="url" value="${url}"/>
             <property name="username" value="${username}"/>
             <property name="password" value="${password}"/>
         </dataSource>
     </environment>
 </environments>

 <mappers>
     <!--映射文件必须要配置-->
     <mapper resource="com/cvs/mapper/SysRoleMapper.xml"></mapper>
 </mappers>
</configuration>
#database.properties配置文件
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/cvs_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username=root
password=root
//步骤3、创建实体类-POJO
package com.cvs.pojo;
import java.util.Date;
/**
 * 角色类
 */
public class SysRole {
	private Integer id;   			//id
	private String code;			//角色编码
	private String roleName; 		//角色名称
	private Integer createdUserId;	//创建者
	private Date createdTime; 		//创建时间
	private Integer updatedUserId;	//更新者
	private Date updatedTime;		//更新时间
	
	//省略getter/setter...
}
//步骤4、DAO层 (mapper)接口
package com.cvs.mapper;
import com.cvs.pojo.SysRole;
import java.util.List;
/**
 * 系统用户角色接口
 * @author Aiden
 */
public interface SysRoleMapper {
    /**
     * 查询所有系统角色
     * @return
     */
    List<SysRole> getSysRoleAll();
}
<!--步骤4、创建与接口对应的-SQL映射文件(SysRoleMapper.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">
<!--namespace:命名空间 java接口文件全包名-->
<mapper namespace="com.cvs.mapper.SysRoleMapper">
    <!--查询所有系统角色-->
    <select id="getSysRoleAll" resultType="com.cvs.pojo.SysRole">
        select * from t_sys_role
    </select>
</mapper>
import com.cvs.mapper.SysRoleMapper;
import com.cvs.pojo.SysRole;
import com.cvs.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
/**
 * 单元测试类
 * @author Aiden
 */
public class SysRoleMapperTest {

    /**
     * 步骤5、创建测试类
	 *​①读取核心配置文件mybatis-config.xml
	 *​②创建SqlSessionFactory对象,读取配置文件
	 *​③创建SqlSession对象
	 *​④调用mapper文件进行数据操作
     */
    
    @Test
    public void getSysRoleAll() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        List<SysRole> sysRoleList = sqlSession.getMapper(SysRoleMapper.class).getSysRoleAll();
        sysRoleList.forEach(r->{
            System.out.println(r.getId()+"\t"+r.getRoleName());
        });
        MyBatisUtils.closeSqlSession(sqlSession);
    }
}
package com.cvs.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;

/**
 * MyBatis工具辅助类
 * @author Aiden
 */
public class MyBatisUtils {

    /**
     * 获取 SqlSession
     * @return
     */
    public static SqlSession getSqlSession() {
        SqlSession sqlSession = null;
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            sqlSession = sqlSessionFactory.openSession();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sqlSession;
    }

    /**
     * 关闭 sqlSession
     * @param sqlSession
     */
    public static void closeSqlSession(SqlSession sqlSession) {
        if (sqlSession != null) {
            sqlSession.close();
        }
    }
}
1.4.MyBatis-Spring

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。

当使用maven构建时必须引入以下配置

<dependency>
 <groupId>org.mybatis</groupId>
 <artifactId>mybatis-spring</artifactId>
 <version>2.0.6</version>
</dependency>

当我们想要一起使用Spring和MyBatis时,需要在 Spring 应用上下文中定义 SqlSessionFactory 和一个数据映射器类

在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!--注入SqlSessionFactoryBean-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--配置数据源-->
        <property name="dataSource" ref="dataSource"></property>
        <!-- 配置别名-->
        <property name="typeAliasesPackage" value="com.cvs.pojo"></property>
        <!--引用mybatis核心配置文件-->
        <property name="configLocation" value="classpath:mybatis-config2.xml"></property>
        <!-- 配置mapper映射文件-->
        <property name="mapperLocations">
            <list>
                <value>com/cvs/mapper/*.xml</value>
            </list>
        </property>
    </bean>

二、基本整合方式

2.1.基本整合方式2-1

在这里插入图片描述

思路梳理

  • ​ 以上流程可以全部移交给Spring来处理
  • ​ 读取配置文件、组件的创建、组件之间的依赖关系以及整个框架的生命周期都由Spring容器统一管理
2.2.基本整合方式2-2

Spring和MyBatis的整合步骤

  1. 整合所需的依赖及配置
  2. 通过Spring配置文件配置数据源
  3. 通过Spring配置文件创建SqlSessionFactory
  4. 通过SqlSessionTemplate操作数据库

使用SqlSessionDaoSupport简化编码

使用Spring配置数据源时,首先选择一种具体的数据源实现技术

  • dbcp

  • c3p0

  • proxool

  • druid

dbcp:DBCP是一个依赖Jakarta commons-pool对象池机制的数据库连接池.DBCP可以直接的在应用程序中使用,Tomcat的数据源使用的就是DBCP。

c3p0:c3p0是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。

druid :阿里出品,淘宝和支付宝专用数据库连接池,但它不仅仅是一个数据库连接池,它还包含一个ProxyDriver,一系列内置的JDBC组件库,一个SQL Parser。支持所有JDBC兼容的数据库,包括 Oracle、MySql、Derby、Postgresql、SQL Server、H2等等。

Druid针对Oracle和MySql做了特别优化,比如Oracle的PS Cache内存占用优化,MySql的ping检测优化。

Druid提供了MySql、Oracle、Postgresql、SQL-92的SQL的完整支持,这是一个手写的高性能SQL Parser,支持Visitor模式,使得分析SQL的抽象语法树很方便。

简单SQL语句用时10微秒以内,复杂SQL用时30微秒。

通过Druid提供的SQL Parser可以在JDBC层拦截SQL做相应处理,比如说分库分表、审计等。Druid防御SQL注入攻击的WallFilter就是通过Druid的SQL Parser分析语义实现的。

2.3.实现Spring对MyBatis的整合

log4j配置

log4j.rootLogger=DEBUG,CONSOLE,file
#log4j.rootLogger=ERROR,ROLLING_FILE
log4j.logger.cn.cvs.dao=debug
log4j.logger.com.ibatis=debug 
log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=debug 
log4j.logger.com.ibatis.common.jdbc.ScriptRunner=debug 
log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=debug 
log4j.logger.java.sql.Connection=debug 
log4j.logger.java.sql.Statement=debug 
log4j.logger.java.sql.PreparedStatement=debug 
log4j.logger.java.sql.ResultSet=debug 
log4j.logger.org.tuckey.web.filters.urlrewrite.UrlRewriteFilter=debug
##################################################################################
# Console Appender  日志在控制输出配置
##################################################################################
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.Threshold=error
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern= [%p] %d %c - %m%n
##################################################################################
# DailyRolling File  每天产生一个日志文件,文件名格式:log2021-12-11
##################################################################################
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.DatePattern=yyyy-MM-dd
log4j.appender.file.File=log.log
log4j.appender.file.Append=true
log4j.appender.file.Threshold=error
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-M-d HH:mm:ss}%x[%5p](%F:%L)%m%n
log4j.logger.com.opensymphony.xwork2=error  

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>
 	<!--类型别名 -->
 	<typeAliases>
    		 <package name="cn.cvs.pojo" />
 	</typeAliases>
</configuration>

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:p="http://www.springframework.org/schema/p"
    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.xsd
 http://www.springframework.org/schema/aop
 http://www.springframework.org/schema/aop/spring-aop.xsd
 http://www.springframework.org/schema/tx
 http://www.springframework.org/schema/tx/spring-tx.xsd ">

 <!--配置数据源-->
 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
              destroy-method="close">
     <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
     <property name="url" value="jdbc:mysql://localhost:3306/cvs_db?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai"/>
     <property name="username" value="root" />
     <property name="password" value="root" />
 </bean>

 <!-- 配置SqlSessionFactoryBean -->
 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
     <!-- 引用数据源组件 -->
     <property name="dataSource" ref="dataSource" />
     <!-- 引用MyBatis配置文件中的配置 -->
     <property name="configLocation" value="classpath:mybatis-config.xml" />
     <!-- 配置SQL映射文件信息 -->
     <property name="mapperLocations">
         <list>
             <value>classpath:cn/cvs/dao/*/*.xml</value>
         </list>
     </property>
 </bean>
 <!-- 配置SqlSessionTemplate -->
 <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
     <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory" />
 </bean>
 <!-- 配置DAO -->
 <bean id="sysUserMapper" class="cn.cvs.dao.sysUser.SysUserMapperImpl">
     <property name="sqlSession" ref="sqlSessionTemplate" />
 </bean>
 <!-- 配置业务Bean -->
 <bean id="sysUserService" class="cn.cvs.service.sysUser.SysUserServiceImpl">
     <property name="sysUserMapper" ref="sysUserMapper" />
 </bean>
</beans>

pojo.SysUser实体类

package cn.cvs.pojo;

import java.util.Date;
//用户实体类
public class SysUser {
	private Integer id; //id 
	private String account; //用户编码
	private String realName; //用户名称
	private String password; //用户密码
	private Integer sex;  //性别
	private Date birthday;  //出生日期
	private String phone;   //电话
	private String address; //地址
	private Integer roleId;    //用户角色ID
	private Integer createdUserId;   //创建者
	private Date createdTime; //创建时间
	private Integer updatedUserId;     //更新者
	private Date updatedTime;   //更新时间

	private Integer age;//年龄
	private String roleIdName; //用户角色名称

	//省略getter/setter
 ....
	}
}

dao接口

package cn.cvs.dao;
import cn.cvs.pojo.SysUser;
import java.util.List;
//dao接口
public interface SysUserMapper {
	/**
	 * 查询用户列表(参数:对象入参)
	 * @param sysUser
	 * @return
	 */
	public List<SysUser> selectSysUserList(SysUser sysUser);
}

dao.impl

ackage cn.cvs.dao;

import java.util.List;
import cn.cvs.pojo.SysUser;
import org.mybatis.spring.SqlSessionTemplate;

//用户数据层实现
public class SysUserMapperImpl implements SysUserMapper {
 private SqlSessionTemplate sqlSession;

 @Override
 public List<SysUser> selectSysUserList(SysUser sysUser) {
     return sqlSession.selectList(
             "cn.cvs.dao.SysUserMapper.selectSysUserList", sysUser);
 }

 //getter
 public SqlSessionTemplate getSqlSession() {
     return sqlSession;
 }
	//setter
 public void setSqlSession(SqlSessionTemplate sqlSession) {
     this.sqlSession = sqlSession;
 }

}

mapper.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">

<mapper namespace="cn.cvs.dao.SysUserMapper">
	<!-- 当数据库中的字段信息与对象的属性不一致时需要通过resultMap来映射 -->
	<resultMap type="SysUser" id="SysUserResult">
		<result property="roleIdName" column="roleName"/>
	</resultMap>

	<!-- 查询用户列表-roleId为null时查询到0条数据 -->
	<select id="selectSysUserList" parameterType="SysUser"resultMap="SysUserResult">
		select u.*,r.roleName from t_sys_user u,t_sys_role r
		where u.roleId = r.id
			and u.roleId = #{roleId}
			and u.realName like CONCAT ('%',#{realName},'%')
	</select>
</mapper>

service

package cn.cvs.service;

import cn.cvs.pojo.SysUser;
import java.util.List;
//业务接口
public interface SysUserService {
 	public List<SysUser> getList(SysUser sysUser);
}

service.impl

package cn.cvs.service;

import cn.cvs.dao.SysUserMapper;
import cn.cvs.pojo.SysUser;

import java.util.List;
//业务实现类
public class SysUserServiceImpl implements SysUserService {
 	private SysUserMapper sysUserMapper;

 	@Override
 	public List<SysUser> getList(SysUser sysUser) {
     		try {
         		return sysUserMapper.selectSysUserList(sysUser);
     		} catch (RuntimeException e) {
         		e.printStackTrace();
         		throw e;
     		}
 	}

 	public SysUserMapper getSysUserMapper() {
     		return sysUserMapper;
 	}

 	public void setSysUserMapper(SysUserMapper sysUserMapper) {
     		this.sysUserMapper = sysUserMapper;
 	}

}

测试类

package cn.cvs.test;

import java.util.ArrayList;
import java.util.List;

import cn.cvs.pojo.SysUser;
import org.apache.log4j.Logger;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.cvs.service.SysUserService;

public class SysUserTest {

 private Logger logger = Logger.getLogger(SysUserTest.class);

 @Test
public void testGetUserList() {
     ApplicationContext ctx = new ClassPathXmlApplicationContext(
             "applicationContext.xml");
     SysUserService userService = (SysUserService) ctx.getBean("sysUserService");
     List<SysUser> userList = new ArrayList<SysUser>();
     SysUser sysUser = new SysUser();
     sysUser.setRealName("赵");
     sysUser.setRoleId(2);
     userList = userService.getList(sysUser);

     for (SysUser user : userList) {
         logger.debug("realName: "+ user.getRealName());
                 
     }
 }
}

案例1中存在的问题描述:

  • 在roleId为null时,期望可以正常查询到realName中包含"赵"的用户,但是查询并没有获取正确的结果。这是什么原因造成的呢?

分析原因:

  • 将控制台输出的SQL语句与参数拼接,会发现其中包含过滤语句 and u.roleId = null,表示查询要满足条件roleId = null的数据。显然不符合最初的需求。

如何解决:

  • 使用if标签改造

三、映射器整合方式

3.1.映射器整合方式
  • MyBatis中可以使用SqlSession的getMapper(Class<T> type)方法,根据指定的映射器和映射文件直接生成实现类
  • MyBatis-Spring提供的MapperFactoryBean能够以配置的方式生成映射器的实现类
<bean id="sysUserMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
	<!--指定映射器,只能是接口类型-->
    <property name="mapperInterface" value="cn.cvs.dao.SysUserMapper"/>
       <!-- 注入SqlSessionFactory以提供SqlSessionTemplate实例-->
	<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
  • 注意: 映射器对应的SQL映射文件与映射器的类路径相同,该映射文件可以自动被MapperFactoryBean解析
3.2.使用MapperFactoryBean注入映射器
<?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:p="http://www.springframework.org/schema/p"
    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.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd ">

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/cvs_db?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root" />
        <property name="password" value="root" />
    </bean>

    <!-- 配置SqlSessionFactoryBean -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 引用数据源组件 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 引用MyBatis配置文件中的配置 -->
        <property name="configLocation" value="classpath:mybatis-config.xml" />
        <!-- 配置SQL映射文件信息 -->
        <property name="mapperLocations">
            <list>
                <value>classpath:cn/cvs/dao/*/*.xml</value>
            </list>
        </property>
    </bean>
    <!-- 配置DAO -->
    <bean id="sysUserMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" value="cn.cvs.dao.sysUser.SysUserMapper"/>
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>
    <!-- 配置业务Bean -->
    <bean id="sysUserService" class="cn.cvs.service.sysUser.SysUserServiceImpl">
        <property name="sysUserMapper" ref="sysUserMapper" />
    </bean>
</beans>
3.3.映射器整合方式

问题:

  • 如果映射器很多的话,相应的配置项也会很多,如何简化配置工作量?

分析:

  • 使用MapperScannerConfigurer注入映射器
    • 自动扫描指定包下的Mapper接口,并将它们直接注册为MapperFactoryBean
3.4.映射器整合方式3-3

MapperScannerConfigurer注入映射器

  • MapperScannerConfigurer递归扫描基准包下所有接口,若它们在SQL映射文件中定义过,则动态注册为映射器实现类
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<property name="basePackage" value="cn.cvs.dao"/>
</bean>
<context:component-scan base-package="cn.cvs.service">
@Service("sysUserService")
public class SysUserServiceImpl implements SysUserService {
    //MapperScannerConfigurer与@Autowired或@Resource注解配合使用,自动创建映射器实现并注入业务组件
	@Autowired // @Resource
	private SysUserMapper sysUserMapper;
	//……代码省略
}

四、配置声明式事务

4.1.配置声明式事务

问题:

  • 如何在业务方法中进行事务控制?

分析:

  • 硬编码方式
    • 弊端
      • 事务相关代码分散在业务方法中难以重用
      • 复杂事务的编码难度高,增加了开发难度
      • Spring基于AOP实现声明式事务处理机制
    • 优势
      • 全在配置文件完成,将事务处理与业务代码分离,降低了开发和维护难度
4.2.配置声明式事务
  • 声明式事务关注的核心问题是
  • 对哪些方法,采取什么样的事务策略

配置步骤

  1. 导入命名空间
  2. 定义事务管理器
  3. 设置事务属性
  4. 定义事务切面

五、事务属性说明

5.1.事务属性说明

propagation:事务传播机制

  • REQUIRED(默认值) : 如果存在一个事务则支持当前事务;如果当前没有事务,则开启一个新的事务。该值可满足大多数的事务需求,可以作为首选的事务传播行为

  • REQUIRES_NEWS :总是开启一个新的事务,如果已存在,则先挂起,然后开启新的事务执行该方法

  • MANDATORY:如果存在一个事务则支持当前事务;如果当前没有事务,则抛出异常

  • NESTED:如果当前存在一个活动事务,则创建一个事务作为当前事物的嵌套事务运行,如果没有当前事务,该取值与REQUIRED相同。

  • SUPPORTS:如果存在一个事务,则支持当前事务,如果当前没有事务,则按非事务方式执行。

  • NOT_SUPPORTED:总以非事务方式执行,如果已存在,则先挂起,然后开启新的事务执行该方法

  • NEVER :总以非事务方式执行如果存在一个活动事务,则抛出异常。

isolation:事务隔离级别

  • DEFAULT(默认值)
  • READ_UNCOMMITTED (读未提交)
  • READ_COMMITTED (提交已读)
  • REPEATABLE_READ (可重复读)
  • SERIALIZABLE (串行读)
5.2.事务属性说明

timeout:事务超时时间

  • 允许事务运行的最长时间,以秒为单位
  • 超过给定的时间自动回滚,防止事务执行时间过长而影响系统性能
  • 在此我们分析下DataSourceTransactionManager;首先开启事物会调用其doBegin方法:
@Override
	protected void doBegin(Object transaction, TransactionDefinition definition) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
		Connection con = null;

		try {
			if (!txObject.hasConnectionHolder() ||
					txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
				Connection newCon = obtainDataSource().getConnection();
				if (logger.isDebugEnabled()) {
					logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
				}
				txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
			}

			txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
			con = txObject.getConnectionHolder().getConnection();

			Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
			txObject.setPreviousIsolationLevel(previousIsolationLevel);
			txObject.setReadOnly(definition.isReadOnly());

			// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
			// so we don't want to do it unnecessarily (for example if we've explicitly
			// configured the connection pool to set it already).
			if (con.getAutoCommit()) {
				txObject.setMustRestoreAutoCommit(true);
				if (logger.isDebugEnabled()) {
					logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
				}
				con.setAutoCommit(false);
			}

			prepareTransactionalConnection(con, definition);
			txObject.getConnectionHolder().setTransactionActive(true);

			int timeout = determineTimeout(definition);
			if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
                //========================
				txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
			}

			// Bind the connection holder to the thread.
			if (txObject.isNewConnectionHolder()) {
				TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
			}
		}

		catch (Throwable ex) {
			if (txObject.isNewConnectionHolder()) {
				DataSourceUtils.releaseConnection(con, obtainDataSource());
				txObject.setConnectionHolder(null, false);
			}
			throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
		}
	}
  • 其中determineTimeout用来获取我们设置的事务超时时间;然后设置到ConnectionHolder对象上(其是ResourceHolder子类),接着看ResourceHolderSupport的setTimeoutInSeconds实现:
/**
	 * Set the timeout for this object in seconds.
	 * @param seconds number of seconds until expiration
	 */
	public void setTimeoutInSeconds(int seconds) {
		setTimeoutInMillis(seconds * 1000L);
	}

	/**
	 * Set the timeout for this object in milliseconds.
	 * @param millis number of milliseconds until expiration
	 */
	public void setTimeoutInMillis(long millis) {
		this.deadline = new Date(System.currentTimeMillis() + millis);
	}
  • 可以看到,其会设置一个deadline时间;用来判断事务超时时间的;那什么时候调用呢?首先检查该类中的代码,会发现:
/**
 * Return the time to live for this object in seconds.
 * Rounds up eagerly, e.g. 9.00001 still to 10.
 * @return number of seconds until expiration
 * @throws TransactionTimedOutException if the deadline has already been reached
 */
public int getTimeToLiveInSeconds() {
	double diff = ((double) getTimeToLiveInMillis()) / 1000;
	int secs = (int) Math.ceil(diff);
	checkTransactionTimeout(secs <= 0);
	return secs;
}
/**
 * Return the time to live for this object in milliseconds.
 * @return number of milliseconds until expiration
 * @throws TransactionTimedOutException if the deadline has already been reached
 */
public long getTimeToLiveInMillis() throws TransactionTimedOutException{
	if (this.deadline == null) {
		throw new IllegalStateException("No timeout specified for this resource holder");
	}
	long timeToLive = this.deadline.getTime() - System.currentTimeMillis();
	checkTransactionTimeout(timeToLive <= 0);
	return timeToLive;
}
/**
 * Set the transaction rollback-only if the deadline has been reached,
 * and throw a TransactionTimedOutException.
 */
private void checkTransactionTimeout(boolean deadlineReached) throws TransactionTimedOutException {
	if (deadlineReached) {
		setRollbackOnly();
		throw new TransactionTimedOutException("Transaction timed out: deadline was " + this.deadline);
	}
}
  • 会发现在调用getTimeToLiveInSeconds和getTimeToLiveInMillis,会检查是否超时,如果超时设置事务回滚,并抛出TransactionTimedOutException异常。到此我们只要找到调用它们的位置就好了,那什么地方调用的它们呢? 最简单的办法使用如“IntelliJ IDEA”中的“Find Usages”找到get***的使用地方;会发现:

  • DataSourceUtils.applyTransactionTimeout会调用DataSourceUtils.applyTimeout,

    • DataSourceUtils.applyTimeout代码如下:
    public static void applyTimeout(Statement stmt, @Nullable DataSource dataSource, int timeout) throws SQLException {
    		Assert.notNull(stmt, "No Statement specified");
    		ConnectionHolder holder = null;
    		if (dataSource != null) {
    			holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
    		}
    		if (holder != null && holder.hasTimeout()) {
    			// Remaining transaction timeout overrides specified value.
    			stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());
    		}
    		else if (timeout >= 0) {
    			// No current transaction timeout -> apply specified value.
    			stmt.setQueryTimeout(timeout);
    		}
    	}
    

    结论:Spring事务超时 = 事务开始时到最后一个Statement创建时时间 + 最后一个Statement的执行时超时时间(即其queryTimeout)。

read-only:事务是否为只读

  • 默认值为false,对于仅执行查询功能的事务设置为true,提高事务处理性能

rollback-for:设定能够触发回滚的异常类型

  • Spring默认只在抛出RuntimeException时才标识事务回滚
  • 可以通过全限定类名自行指定需要回滚事务的异常

no-rollback-for:设定不触发回滚的异常类型

  • Spring默认CheckedException不会触发事务回滚
  • 可以通过全限定类名自行指定不需回滚事务的异常

六、使用注解实现声明式事务

6.1.使用注解实现声明式事务

在Spring配置文件中配置事务管理类,并开启注解处理事务功能

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务管理器的id被定义为transactionManager,则<tx:annotation-driven/>不需要指定transaction-manager属性的值-->
<tx:annotation-driven />

使用==@Transactional==为方法添加事务支持

@Transactional
@Service("sysUserService")
public class SysUserServiceImpl implements SysUserService {
	……
}
6.2.使用注解实现声明式事务
属性类型说明
propagation枚举型:Propagation可选的传播性设置。使用举例:@Transactional(propagation=Propagation.REQUIRES_NEW)
isolation枚举型:Isolation可选的隔离性级别。使用举例:@Transactional(isolation=Isolation.READ_COMMITTED)
timeoutint型(以秒为单位)事务超时。使用举例:@Transactional(timeout=10)
readOnly布尔型是否为只读型事务。使用举例:@Transactional(readOnly=true)
6.3.使用注解实现声明式事务3-3
属性类型说明
rollbackFor一组Class类的实例,必须是Throwable的子类一组异常类,遇到时必须进行回滚。使用举例:@Transactional(rollbackFor={SQLException.class} ),多个异常可用英文逗号隔开
rollbackForClassName一组Class类的实例,必须是Throwable的子类一组异常类名,遇到时必须进行回滚。使用举例:@Transactional(rollbackForClassName={“SQLException”} ),多个异常可用英文逗号隔开
noRollbackFor一组Class类的实例,必须是Throwable的子类一组异常类,遇到时必须不回滚
noRollbackForClassName一组Class类的实例,必须是Throwable的子类一组异常类名,遇到时必须不回滚

示例代码

1、Mapper接口

package cn.cvs.dao.sysUser;

import cn.cvs.pojo.SysUser;
import java.util.List;

public interface SysUserMapper {
	
 /**
     * 查询用户列表(参数:对象入参)
  *
     * @param sysUser
     * @return
  */
 public List<SysUser> selectSysUserList(SysUser sysUser);

 /**
     * 保存用户
  *
     * @param sysUser
     * @return
  */
 public int add(SysUser sysUser);
}

2、Mapper.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">

<mapper namespace="cn.cvs.dao.sysUser.SysUserMapper">
	<!-- 当数据库中的字段信息与对象的属性不一致时需要通过resultMap来映射 -->
	<resultMap type="SysUser" id="SysUserResult">
		<result property="roleIdName" column="roleName"/>
	</resultMap>
	<!-- 查询用户列表-roleId为null时查询到0条数据 -->
	<select id="selectSysUserList" parameterType="cn.cvs.pojo.SysUser" resultMap="SysUserResult">
		select u.*,r.roleName from t_sys_user u,t_sys_role r
		where u.roleId = r.id
			and u.roleId = #{roleId}
			and u.realName like CONCAT ('%',#{realName},'%')
	</select>
	<!-- 增加用户 -->
	<insert id="add" parameterType="SysUser">
     insert into t_sys_user (account,realName,password,sex
     	,birthday,phone,address,roleId,createdUserId,createdTime)
		values (#{account},#{realName},#{password},#{sex},#{birthday}
			,#{phone},#{address},#{roleId},#{createdUserId},#{createdTime})
 </insert>
</mapper>

3、service接口

package cn.cvs.service.sysUser;

import cn.cvs.pojo.SysUser;
import java.util.List;

public interface SysUserService {
 List<SysUser> getList(SysUser sysUser);

 boolean add(SysUser sysUser);
}

4、service实现类

package cn.cvs.service.sysUser;

import cn.cvs.dao.sysUser.SysUserMapper;
import cn.cvs.pojo.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

/**
 * 用户模块业务层实现
*/
@Transactional
@Service("sysUserService")
public class SysUserServiceImpl implements SysUserService {
 @Autowired // @Resource
 private SysUserMapper sysUserMapper;

 @Override
 public List<SysUser> getList(SysUser sysUser) {
     try {
         return sysUserMapper.selectSysUserList(sysUser);
     } catch (RuntimeException e) {
         e.printStackTrace();
         throw e;
     }
 }

 @Override
 public boolean add(SysUser sysUser) {
     boolean result = false;
     try {
         if (sysUserMapper.add(sysUser) == 1){
             result = true;
             // 测试事物回滚时,打开注释
              throw new RuntimeException();
         }
     } catch (RuntimeException e) {
         e.printStackTrace();
         throw e;
     }
     return result;
 }
}

5、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.xsd
 http://www.springframework.org/schema/context
 http://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
 http://www.springframework.org/schema/aop/spring-aop.xsd">

 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
       destroy-method="close">
     <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
     <property name="url" value="jdbc:mysql://127.0.0.1:3306/cvs_db?useUnicode=true
                     &amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai"/>
     <property name="username" value="root"/>
     <property name="password" value="123456"/>
 </bean>

 <!-- 配置SqlSessionFactoryBean -->
 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
     <!-- 引用数据源组件 -->
     <property name="dataSource" ref="dataSource"/>
     <!-- 引用MyBatis配置文件中的配置 -->
     <property name="configLocation" value="classpath:mybatis-config.xml"/>
 </bean>
 <!-- 配置DAO -->
 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
     <!-- <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> -->
     <property name="basePackage" value="cn.cvs.dao"/>
 </bean>
 <!-- 配置业务Bean -->
 <!-- <bean id="userService" class="cn.cvs.service.user.UserServiceImpl">
     <property name="userMapper" ref="userMapper" />
 </bean> -->
 <context:component-scan base-package="cn.cvs.service"/>
    
    
 <!--1.定义事务管理器 -->
 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <property name="dataSource" ref="dataSource"></property>
 </bean>
 <!--2.启注解处理事务功能-->
 <tx:annotation-driven/>
 <!--3.配置事务增强,设置事务属性-->
 <tx:advice id="txAdvice">
     <tx:attributes>
         <tx:method name="get*" propagation="SUPPORTS"/>
         <tx:method name="add*" propagation="REQUIRED"/>
         <tx:method name="del*" propagation="REQUIRED"/>
         <tx:method name="update*" propagation="REQUIRED"/>
         <tx:method name="*" propagation="REQUIRED"/>
     </tx:attributes>
 </tx:advice>
 <!-- 定义切面 -->
 <aop:config>
     <aop:pointcut id="serviceMethod"
                   expression="execution(* cn.cvs.service..*.*(..))"/>
     <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod"/>
 </aop:config>
</beans>

6、测试类

package cn.cvs.test.sysUser;

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


import cn.cvs.pojo.SysUser;
import org.apache.log4j.Logger;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.cvs.service.sysUser.SysUserService;

public class SysUserTest {

 private Logger logger = Logger.getLogger(SysUserTest.class);

 @Test
 public void testAddUser() throws ParseException {
     ApplicationContext ctx = new ClassPathXmlApplicationContext(
             "applicationContext.xml");
     SysUserService userService = (SysUserService) ctx.getBean("sysUserService");

     SysUser user = new SysUser();
     user.setAccount("test001");
     user.setRealName("测试用户001");
     user.setPassword("1234567");
     Date birthday =new SimpleDateFormat("yyyy-MM-dd").parse("2004-12-12");
     user.setBirthday(birthday);
     user.setAddress("地址测试");
     user.setSex(1);
     user.setPhone("13688783697");
     user.setRoleId(1);
     user.setCreatedUserId(1);
     user.setCreatedTime(new Date());

     boolean result = userService.add(user);
     logger.debug("testAdd result : " + result);
 }
}

七、本章总结

在这里插入图片描述


扩展 配置druid数据源
druid的maven依赖配置
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.9</version>
</dependency>
druid的数据源配置文件(database_druid.properties)
druid.driverClassName=com.mysql.cj.jdbc.Driver
druid.url=jdbc:mysql://localhost:3306/cvs_db?useUnicode=true&allowPublicKeyRetrieval=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
druid.username=root
druid.password=root
druid.initialSize=10
druid.minIdle=6
druid.maxActive=50
druid.maxWait=60000
druid.timeBetweenEvictionRunsMillis=60000
druid.minEvictableIdleTimeMillis=300000
druid.validationQuery=SELECT 'x'
druid.testWhileIdle=true
druid.testOnBorrow=false
druid.testOnReturn=false
druid.poolPreparedStatements=false
druid.maxPoolPreparedStatementPerConnectionSize=20
druid.filters=wall,stat
druid在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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.2.xsd   http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan
            base-package="com.aiden.mybatisspring.mapper,com.aiden.mybatisspring.service"></context:component-scan>

    <!--1.引入druid配置文件  database_druid.properties文件在resources下-->
    <context:property-placeholder location="classpath:database_druid.properties"></context:property-placeholder>
    
    <!--2.配置Druid数据源-->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!--高版本的Driver可以自动识别数据库  而不再需要指定具体是哪一个Driver了-->
        <property name="driverClassName" value="${druid.driverClassName}"/>
        <property name="url" value="${druid.url}"/>
        <property name="username" value="${druid.username}"/>
        <property name="password" value="${druid.password}"/>
        <!-- 初始化连接数量 -->
        <property name="initialSize" value="${druid.initialSize}"/>
        <!-- 最小空闲连接数 -->
        <property name="minIdle" value="${druid.minIdle}"/>
        <!-- 最大并发连接数 -->
        <property name="maxActive" value="${druid.maxActive}"/>
        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="${druid.maxWait}"/>
        <!--以下暂时可以不需要配置-->
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}"/>
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}"/>
        <property name="validationQuery" value="${druid.validationQuery}"/>
        <property name="testWhileIdle" value="${druid.testWhileIdle}"/>
        <property name="testOnBorrow" value="${druid.testOnBorrow}"/>
        <property name="testOnReturn" value="${druid.testOnReturn}"/>
        <!-- 打开PSCache,并且指定每个连接上PSCache的大小 如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。 -->
        <property name="poolPreparedStatements" value="${druid.poolPreparedStatements}"/>
        <property name="maxPoolPreparedStatementPerConnectionSize"
                  value="${druid.maxPoolPreparedStatementPerConnectionSize}"/>
        <!-- 配置监控统计拦截的filters -->
        <property name="filters" value="${druid.filters}"/>
    </bean>
    
    <!--3.配置SqlSessionFactoryBean-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--3.1引用数据源组件druidDataSource -->
        <property name="dataSource" ref="druidDataSource"></property>
        <!--3.2引用MyBatis配置文件中的配置 -->
        <property name="configLocation" value="classpath:mybatis-config2.xml"></property>
        <!--3.3设置别名-->
        <property name="typeAliasesPackage" value="com.aiden.mybatisspring.pojo"></property>
        <!--3.4配置SQL映射文件信息 -->
        <property name="mapperLocations">
            <list>
                <value>classpath:com/aiden/mybatisspring/mapper/*.xml</value>
            </list>
        </property>
    </bean>

    <!--4.配置SqlSessionTemplate -->
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>
        
    </bean>
</beans>

八、常见错误

在这里插入图片描述

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

众生云海,一念初见

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值