版本3.5.9
课程案例源代码
https://gitee.com/ossbar/mybatis.git
环境要求
- JDK1.8+
- MySQL8.0.27
- Maven 3.6.1
- IDEA/VSCode
学习建议
MyBatis的学习分为四个阶段:了解---->>入门--->>熟悉--->>拓展
了解阶段:请参考官方指导文档入门学习基础知识。
入门阶段:能把MyBatis技术灵活应用于实际项目中。
熟悉阶段:研究MyBatis原理、 MyBatis源码。
拓展阶段:基于Mybatis框架,进行封装和扩展,例如开发扩展插件:例如Mybatis-plus,Mybatis-generator等。
学习建议:看官方文档,最为详细
知识导图
适宜人群
本教程适合:
- 想要深入学习 MyBatis 框架的小伙伴
- 零散学习过 MyBatis ,但对 MyBatis 整体了解不够深入的小伙伴
- 对 MyBatis 的内部和底层感兴趣的小伙伴
- 有意向以后成为高级开发的小伙伴
- 有打算自己封装一套属于自己的用来做项目开发脚手架的小伙伴
你会学到什么
- 配置文件、Mapper 映射文件的编写和设计
- MyBatis 中的注解使用详解
- MyBatis 中的缓存、事务、插件等机制
- MyBatis 的整体生命周期、执行流程全剖析
- MyBatis 中使用到的设计模式详解
- 自己动手二次封装 MyBatis
Mybatis简介
Mybatis是什么
MyBatis(前身是iBatis) 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射,可以在实体类和 SQL 语句之间建立映射关系,是一种半自动化的 ORM (Object/Relation Mapping,即对象关系映射)实现。其封装性低于 Hibernate,但性能优秀、小巧、简单易学、应用广泛。
优点
- MyBatis 是免费且开源的。
- 与 JDBC 相比,减少了 50% 以上的代码量。
- MyBatis 是最简单的持久化框架,小巧并且简单易学。
- MyBatis 相当灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL 写在 XML 中,和程序逻辑代码分离,降低耦合度,便于同一管理和优化,提高了代码的可重用性。
- 提供 XML 标签,支持编写动态 SQL 语句。
- 提供映射标签,支持对象与数据库的 ORM 字段关系映射。
- 支持存储过程。MyBatis 以存储过程的形式封装 SQL,可以将业务逻辑保留在数据库之外,增强应用程序的可移植性、更易于部署和测试。
缺点
- 编写 SQL 语句工作量较大,对开发人员编写 SQL 语句的功底有一定要求。
- SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
Mybatis能干什么
MyBatis 专注于 SQL 本身,是一个足够灵活的 DAO 层解决方案。适用于性能要求高,且需求变化较多的项目,如互联网项目。
安装Mybatis
方法一:下载:github.com/mybatis/myb…
点击Mybatis-3.5.9.zip进行下载,下载后解压目录如下:
使用MyBatis框架非常简单,只需在应用程序中引入MyBatis的核心包和lib目录中的依赖包即可。
注意:如果底层采用的是MySQL数据库,那么还需要将MySQL数据库的驱动JAR包添加到应用程序的类路径中;如果采用其他类型的数据库,则同样需要将对应类型的数据库驱动包添加到应用程序的类路径中。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
<scope>runtime</scope>
</dependency>
方法二:如果基于Maven工程使用Mybatis持久化框架,在工程的pom.xml文件中引入如下依赖
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
Mybatis工作原理
使用Mybatis之前,首先要了解Mybatis原理和工作流程。
MyBatis组件以及执行基本流程
- SqlSessionFactoryBuilder(构造器):它会根据配置信息或者代码生成SqlSessionFactory(工厂接口)
- SqlSessionFactory:依靠工厂来生成SqlSession。
- SqlSession:是一个既可以发送SQL去执行并返回结果的,也可以获取Mapper接口,通过Mapper接口查询并封装数据。
- SQL Mapper:它是MyBatis新设计的组件,它是由一个Java接口和XML文件(或者注解)构成的,需要给出对应的SQL和映射规则。它负责发送SQL去执行,并返回结果。
用下图表达上述组件之间的关联
Mybatis核心对象
什么是SqlSessionFactory?
•SqlSessionFactory是MyBatis框架中十分重要的对象,它是单个数据库映射关系经过编译后的内存镜像,其主要作用是创建SqlSession。有点类似于我们在JDBC中使用的Connection
•SqlSessionFactory对象的实例可以通过SqlSessionFactoryBuilder对象来构建,而SqlSessionFactoryBuilder则可以通过XML配置文件或一个预先定义好的Configuration实例构建出SqlSessionFactory的实例。
构建SqlSessionFactory
•通过XML配置文件构建出的SqlSessionFactory实例现代码如下:
InputStream inputStream = Resources.getResourceAsStream("配置文件位置");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
•SqlSessionFactory对象是线程安全的,它一旦被创建,在整个应用执行期间都会存在。如果我们多次的创建同一个数据库的SqlSessionFactory,那么此数据库的资源将很容易被耗尽。为此,通常每一个数据库都会只对应一个SqlSessionFactory,所以在构建SqlSessionFactory实例时,建议使用单列模式。
什么是SqlSession?
•SqlSession是MyBatis框架中另一个重要的对象,它是应用程序与持久层之间执行交互操作的一个单线程对象,其主要作用是执行持久化操作。它可以发送一条SQL语句去执行,并返回结果,从这个角度来说,它有点类似于PrepareStatement。
使用完SqlSession对象后要及时关闭,通常可以将其放在finally块中关闭。
SqlSession中的方法
查询方法:
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
插入、更新和删除方法:
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
其他方法:
void commit(); 提交事务的方法。
void rollback(); 回滚事务的方法。
void close(); 关闭SqlSession对象。
<T> T getMapper(Class<T> type); 返回Mapper接口的代理对象。
Connection getConnection(); 获取JDBC数据库连接对象的方法。
使用工具类创建SqlSession
为了简化开发,通常在实际项目中都会使用工具类来创建SqlSession。
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory = null;
static {
try {
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (Exception e) {
e.printStackTrace();
}
}
public static SqlSession getSession() {
return sqlSessionFactory.openSession();
}
}
Mybatis映射文件
主要元素
在MyBatis框架的核心配置文件中,元素是配置文件的根元素,其他元素都要在元素内配置。
元素
是一个配置属性的元素,该元素通常用来将内部的配置外在化,即通过外部的配置来动态的替换内部定义的属性。例如,数据库的连接等属性,就可以通过典型的Java属性文件中的配置来替换,具体方式如下:
1、编写db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root
2、配置<properties... />属性
<properties resource="db.properties" />
3、修改配置文件中数据库连接的信息
<dataSource type="POOLED">
<!-- 数据库驱动 -->
<property name="driver" value="${jdbc.driver}" />
<!-- 连接数据库的url -->
<property name="url" value="${jdbc.url}" />
<!-- 连接数据库的用户名 -->
<property name="username" value="${jdbc.username}" />
<!-- 连接数据库的密码 -->
<property name="password" value="${jdbc.password}" />
</dataSource>
元素
元素主要用于改变MyBatis运行时的行为,例如开启二级缓存、开启延迟加载等。
这些配置在配置文件中的使用方式如下:
<!-- 设置 -->
<settings>
<setting name="cacheEnabled" value="true" />
<setting name="lazyLoadingEnabled" value="true" />
<setting name="multipleResultSetsEnabled" value="true" />
<setting name="useColumnLabel" value="true" />
<setting name="useGeneratedKeys" value="false" />
<setting name="autoMappingBehavior" value="PARTIAL" />
...
</settings>
小提示:上述配置通常不需要开发人员去配置,作为了解即可。
元素
元素用于为配置文件中的Java类型设置一个简短的名字,即设置别名。别名的设置与XML配置相关,其使用的意义在于减少全限定类名的冗余。
1、使用元素配置别名的方法如下:
<typeAliases>
<typeAlias alias="user" type="com.creatorblue.po.User"/>
</typeAliases>
2、当POJO类过多时,可以通过自动扫描包的形式自定义别名,具体如下:
<typeAliases>
<package name="com.creatorblue.po"/>
</typeAliases>
注意:如果在程序中使用了注解,则别名为其注解的值。
MyBatis框架默认为许多常见的Java类型提供了相应的类型别名,如下表所示。
元素
typeHandler的作用就是将预处理语句中传入的参数从javaType(Java类型)转换为jdbcType(JDBC类型),或者从数据库取出结果时将jdbcType转换为javaType。
元素可以在配置文件中注册自定义的类型处理器,它的使用方式有两种。
1、注册一个类的类型处理器
<typeHandlers>
<typeHandler handler="com.itheima.type.CustomtypeHandler" />
</typeHandlers>
2、注册一个包中所有的类型处理器
<typeHandlers>
<package name="com.itheima.type" />
</typeHandlers>
元素
MyBatis中默认的ObjectFactory的作用是实例化目标类,它既可以通过默认构造方法实例化,也可以在参数映射存在的时候通过参数构造方法来实例化。通常使用默认的ObjectFactory即可。
大部分场景下都不用配置和修改默认的ObjectFactory ,如果想覆盖ObjectFactory的默认行为,可以通过自定义ObjectFactory来实现,具体如下:
1、自定义一个对象工厂
public class MyObjectFactoryextends DefaultObjectFactory {
private static final long serialVersionUID= -4114845625429965832L;
public <T> T create(Class<T> type) {
returnsuper.create(type);
}
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
returnsuper.create(type,constructorArgTypes,constructorArgs);
}
public void setProperties(Properties properties) {
super.setProperties(properties);
}
public <T> booleanisCollection(Class<T> type) {
returnCollection.class.isAssignableFrom(type);
}
}
2、在配置文件中使用元素配置自定义的ObjectFactory
<objectFactory type="com.creatorblue.factory.MyObjectFactory">
<property name="name" value="MyObjectFactory"/>
</objectFactory>
注意:由于自定义ObjectFactory在实际开发时不经常使用,这里只需要了解即可。
元素
MyBatis允许在已映射语句执行过程中的某一点进行拦截调用,这种拦截调用是通过插件来实现的。元素的作用就是配置用户所开发的插件。
如果用户想要进行插件开发,必须要先了解其内部运行原理,因为在试图修改或重写已有方法的行为时,很可能会破坏MyBatis原有的核心模块。
元素
元素用于对环境进行配置。MyBatis的环境配置实际上就是数据源的配置,我们可以通过元素配置多种数据源,即配置多种数据库。
使用元素进行环境配置的示例如下:
<environments default="development">
<environment id="development">
<transactionManagertype="JDBC" />
<dataSourcetype="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
...
</environments>
事务管理器的配置
在MyBatis中,可以配置两种类型的事务管理器,分别是JDBC和MANAGED。关于这两个事务管理器的描述如下:
lJDBC:此配置直接使用了JDBC的提交和回滚设置,它依赖于从数据源得到的连接来管理事务的作用域。
lMANAGED:此配置从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期。默认情况下,它会关闭连接,但一些容器并不希望这样,为此可以将closeConnection属性设置为false来阻止它默认的关闭行为。
注意:如果项目中使用的是Spring+ MyBatis,则没有必要在MyBatis中配置事务管理器,因为实际开发中,会使用Spring自带的管理器来实现事务管理。
元素
元素用于指定MyBatis映射文件的位置,一般可以使用以下4种方法引入映射器文件,具体如下。
1、使用类路径引入
<mappers>
<mapper resource="com/creatorblue/mapper/UserMapper.xml"/>
</mappers>
2、使用本地文件路径引入
<mappers>
<mapper url="file:///D:/com/creatorblue/mapper/UserMapper.xml"/>
</mappers>
3、使用接口类引入
<mappers>
<mapper class="com.creatorblue.mapper.UserMapper"/>
</mappers>
4、使用包名引入
<mappers>
<package name="com.creatorblue.mapper"/>
</mappers>
Mybatis入门程序
查询客户
在实际开发中,查询操作通常都会涉及到单条数据的精确查询,以及多条数据的模糊查询。
根据客户编号查询客户信息
1、MySQL数据库中,创建一个名为mybatis的数据库,在此数据库中创建一个t_customer表,同时预先插入几条数据。
2、在Eclipse中,创建一个名为chapter06的Web项目,将MyBatis的核心JAR包、lib目录中的依赖JAR包,以及MySQL数据库的驱动JAR包一同添加到项目的lib目录下, 并发布到类路径中。
3、由于MyBatis默认使用log4j输出日志信息,所以如果要查看控制台的输出SQL语句,那么就需要在classpath路径下配置其日志文件。在项目的src目录下创建log4j.properties文件。Apache 开源项目 Log4j 的远程代码执行漏洞细节2021年12月被公开,由于 Log4j 的广泛使用,该漏洞一旦被攻击者利用会造成严重危害。据悉,Apache Log4j 2.x <= 2.14.1 版本均回会受到影响,所以建议log4j升级到2.17.0以上版本或使用logback替换log4j
# Global logging configuration
log4j.rootLogger=ERROR, stdout
# MyBatis logging configuration...
log4j.logger.com.creatorblue=DEBUG
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
4、在src目录下,创建一个com.creatorblue.po包,在该包下创建持久化类Customer,并在类中声明id、username、jobs和phone属性,及其对应的getter/setter方法。
public class Customer {
private Integer id;
private String username;
private String jobs;
private String phone
//省略Getter和Setter方法
@Override
public String toString() {
return "Customer [id=" + id + ", username=" + username +
", jobs=" + jobs + ", phone=" + phone + "]";
}
}
5、在src目录下,创建一个com.creatorblue.mapper包,并在包中创建映射文件CustomerMapper.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="com.creatorblue.mapper.CustomerMapper">
<select id="findCustomerById" parameterType="Integer"
resultType="com.creatorblue.po.Customer">
select * from t_customer where id = #{id}
</select>
</mapper>
6、在src目录下,创建MyBatis的核心配置文件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>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/creatorblue/mapper/CustomerMapper.xml"/>
</mappers>
</configuration>
7、在src目录下,创建一个com.creatorblue.test包,在该包下创建测试类MybatisTest,并在类中编写测试方法findCustomerByIdTest()。
public class MybatisTest {
@Test
public void findCustomerByIdTest() throws Exception {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
Customer customer = sqlSession.selectOne("com.creatorblue.mapper.CustomerMapper.findCustomerById”, 1);
System.out.println(customer.toString());
sqlSession.close();
}
}
根据客户名模糊查询客户信息
只需要在映射文件CustomerMapper.xml中,添加根据客户名模糊查询客户信息列表的SQL语句。
<select id="findCustomerByName" parameterType="String" resultType="com.creatorblue.po.Customer">
select * from t_customer where username like '%${value}%'
</select>
添加客户
添加操作是通过元素来实现的。例如,向数据库中的t_customer表中插入一条数据可以通过如下配置来实现。
<insert id="addCustomer" parameterType="com.creatorblue.po.Customer">
insert into t_customer(username,jobs,phone) values(#{username},#{jobs},#{phone})
</insert>
在测试类MybatisTest中,添加测试方法addCustomerTest()。
public void addCustomerTest() throws Exception{
…
Customer customer = new Customer();
customer.setUsername("rose");
customer.setJobs("student");
customer.setPhone("13333533092");
int rows = sqlSession.insert("com.creatorblue.mapper.CustomerMapper.addCustomer", customer);
…
}
更新客户
更新操作在映射文件中是通过配置元素来实现的。
<update id="updateCustomer" parameterType="com.creatorblue.po.Customer">
update t_customer set username=#{username},jobs=#{jobs},phone=#{phone} where id=#{id}
</update>
在测试类MybatisTest中,添加测试方法updateCustomerTest(),
将id为4的用户职业修改为programmer,电话修改为13311111111。
public void updateCustomerTest() throws Exception{
…
Customer customer = new Customer();
customer.setId(4);
customer.setUsername("rose");
customer.setJobs("programmer");
customer.setPhone("13311111111");
int rows = sqlSession.update("com.creatorblue.mapper.CustomerMapper.updateCustomer", customer);
…
}
删除客户
删除操作在映射文件中是通过配置元素来实现的。
<delete id="deleteCustomer" parameterType="Integer">
delete from t_customer where id=#{id}
</delete>
在测试类MybatisTest中,添加测试方法deleteCustomerTest(),该方法用于将id为4的客户信息删除。
public void deleteCustomerTest() throws Exception{
…
int rows = sqlSession.delete("com.creatorblue.mapper.CustomerMapper.deleteCustomer", 4);
…
}
动态SQL语句
动态SQL有什么作用?
开发人员在使用JDBC或其他类似的框架进行数据库开发时,通常都要根据需求去手动拼装SQL,这是一个非常麻烦且痛苦的工作,而MyBatis提供的对SQL语句动态组装的功能,恰能很好的解决这一麻烦工作。
动态SQL是MyBatis的强大特性之一,MyBatis3采用了功能强大的基于OGNL的表达式来完成动态SQL。动态SQL主要元素如下表所示:
IF元素
在MyBatis中,元素是最常用的判断语句,它类似于Java中的if语句,主要用于实现某些简单的条件选择。其基本使用示例如下:
select * from t_customer where 1=1
<if test="username !=null and username !=''">
and username like concat('%',#{username}, '%')
</if>
<if test="jobs !=null and jobs !=''">
and jobs= #{jobs}
</if>
使用元素对username和jobs进行非空判断,并动态组装SQL
在实际应用中,我们可能会通过多个条件来精确的查询某个数据。例如,要查找某个客户的信息,可以通过姓名和职业来查找客户,也可以不填写职业直接通过姓名来查找客户,还可以都不填写而查询出所有客户,此时姓名和职业就是非必须条件。
choose及其子元素
select * from t_customer where 1=1
<choose>
<when test="username !=null and username !=''">
and username like concat('%',#{username}, '%')
</when>
<when test="jobs !=null and jobs !=''">
and jobs= #{jobs}
</when>
<otherwise>
and phone is not null
</otherwise>
</choose>
使用及其子元素依次对条件进行非空判断,并动态组装SQL
where、Trim元素
在前两个小节的案例中,映射文件中编写的SQL后面都加入了“where 1=1”的条件,那么到底为什么要这么写呢?如果将where后“1=1”的条件去掉,那么MyBatis所拼接出来的SQL将会如下所示:
select * from t_customer where and username like concat('%',?, '%')
可以看出上面SQL语句明显存在SQL语法错误,而加入了条件“1=1”后,既保证了where后面的条件成立,又避免了where后面第一个词是and或者or之类的关键词。不过“where 1=1”这种写法对于初学者来将不容易理解,并且也不够雅观。
针对上述情况中“where 1=1”,在MyBatis的SQL中就可以使用或元素进行动态处理。
select * from t_customer
<where>
<if test="username !=null and username !=''">
and username like concat('%',#{username}, '%')
</if>
<if test="jobs !=null and jobs !=''">
and jobs= #{jobs}
</if>
</where>
select * from t_customer
<trim prefix="where" prefixOverrides="and">
<if test="username !=null and username !=''">
and username like concat('%',#{username}, '%')
</if>
<if test="jobs !=null and jobs !=''">
and jobs= #{jobs}
</if>
</trim>
set元素
在Hibernate中,想要更新某个对象,就需要发送所有的字段给持久化对象,这种想更新的每一条数据都要将其所有的属性都更新一遍的方法,其执行效率是非常差的。为此,在MyBatis中可以使用动态SQL中的元素进行处理:
<update id="updateCustomer" parameterType="com.creatorblue.po.Customer">
update t_customer
<set>
<if test="username !=null and username !=''">
username=#{username},
</if>
<if test="jobs !=null and jobs !=''">
jobs=#{jobs},
</if>
</set>
where id=#{id}
</update>
使用和元素对username和jobs进行更新判断,并动态组装SQL。这样就只需要传入想要更新的字段即可
foreach元素
假设如下需求:
在一个客户表中有1000条数据,现在需要将id值小于100的客户信息全部查询出来,这要怎么做呢?
针对上述需求,理想的解决方法就是使用MyBatis中动态SQL的元素进行处理。其基本使用示例如下所示:
<select id="findCustomerByIds" parameterType="List" resultType="com.creatorblue.po.Customer">
select * from t_customer where id in
<foreach item="id" index="index" collection="list"
open="(" separator="," close=")">
#{id}
</foreach>
</select>
关于上述示例中元素中使用的几种属性的描述具体如下:
item:配置的是循环中当前的元素。
index:配置的是当前元素在集合的位置下标。
collection:配置的list是传递过来的参数类型(首字母小写),它可以是一个array、list(或collection)、Map集合的键、POJO包装类中数组或集合类型的属性名等。
open和close:配置的是以什么符号将这些集合元素包装起来。
separator:配置的是各个元素的间隔符。
在使用时最关键也是最容易出错的就是collection属性,该属性是必须指定的,而且在不同情况下,该属性的值是不一样的。主要有以下3种情况:
1、如果传入的是单参数且参数类型是一个数组或者List的时候,collection属性值分别为array和list(或collection)。
2、如果传入的参数是多个的时候,就需要把它们封装成一个Map了,当然单参数也可以封装成Map集合,这时候collection属性值就为Map的键。
3、如果传入的参数是POJO包装类的时候,collection属性值就为该包装类中需要进行遍历的数组或集合的属性名。
bind元素
还记得入门案例中模糊查询的SQL语句么?
select * from t_customer where username like '%${value}%'
上述SQL语句有什么不妥?
1、如果使用“${}”进行字符串拼接,则无法防止SQL注入问题;
2、如果改用concat函数进行拼接,则只针对MySQL数据库有效;
3、如果改用“||”进行字符串拼接,则只针对Oracle数据库有效。
select * from t_customer where username like concat('%',#{value},'%')
小提示:这样,映射文件中的SQL就要根据不同的情况提供不同形式的实现,这显然是比较麻烦的,且不利于项目的移植。为了减少这种麻烦,就可以使用MyBatis的元素来解决这一问题。
MyBatis的元素可以通过OGNL表达式来创建一个上下文变量,其使用方式如下:
<select id="findCustomerByName" parameterType="com.creatorblue.po.Customer"
resultType="com.creatorblue.po.Customer">
<bind name="pattern_username" value="'%'+_parameter.getUsername()+'%'" />
select * from t_customer
where
username like #{pattern_username}
</select>
_parameter.getUsername()表示传递进来的参数(也可以直接写成对应的参数变量名,如username)
需要的地方直接引用元素的name属性值即可
Mybatis的关联映射
为什么学习MyBatis关联关系?
实际的开发中,对数据库的操作常常会涉及到多张表,这在面向对象中就涉及到了对象与对象之间的关联关系。针对多表之间的操作,MyBatis提供了关联映射,通过关联映射就可以很好的处理对象与对象之间的关联关系。本章中,将对MyBatis的关联关系映射进行详细的讲解。
在关系型数据库中,多表之间存在着三种关联关系,分别为一对一、一对多和多对多,如下图所示:
在Java中,通过对象也可以进行关联关系描述,如图下图所示:
1对1关系
在现实生活中,一对一关联关系是十分常见的。例如,一个人只能有一个身份证,同时一个身份证也只会对应一个人。
那么使用MyBatis是怎么处理图中的这种一对一关联关系的呢?
在前面章节所讲解的元素中,包含了一个子元素,MyBatis就是通过该元素来处理一对一关联关系的。
在元素中,通常可以配置以下属性:
使用元素进行一对一关联映射非常简单,只需要参考如下两种示例配置即可。
1对多关系
开发人员接触更多的关联关系是一对多(或多对一)。例如,一个用户可以有多个订单,同时多个订单归一个用户所有。
那么使用MyBatis是怎么处理这种一对多关联关系的呢?
在前面章节所讲解的元素中,包含了一个子元素,MyBatis就是通过该元素来处理一对多关联关系的。
子元素的属性大部分与元素相同,但其还包含一个特殊属性--ofType 。
多对多关系
在实际项目开发中,多对多的关联关系也是非常常见的。以订单和商品为例,一个订单可以包含多种商品,而一种商品又可以属于多个订单。
在数据库中,多对多的关联关系通常使用一个中间表来维护,中间表中的订单id作为外键参照订单表的id,商品id作为外键参照商品表的id。
在MyBatis中,多对多的关联关系查询,同样可以使用前面介绍的元素进行处理(其用法和一对多关联关系查询语句用法基本相同)。
别名的定义
在MyBatis的映射文件中,如果参数类型是一个实体类对象,在parameterType中需要使用此对象类型的全称,类的全称一般都比较长,在编写时即繁琐又容易出错。如下TypeMapper.xml的insert元素
<insert id="insertType" parameterType="com.cbmv.domain.Type">
................
</insert>
此时可以为这些类型定义别名,在xml中对应的位置使用别名,别名的定义使用MyBatis配置文件中typeAliases元素,它的子元素typeAlias用于定义每一个别名,
typeAlias元素的属性
| type | 类或接口的全称 |
| alias | 别名 |
示例
<typeAliases>
<typeAlias type="com.cbmv.domain.Type" alias="Type"/>
</typeAliases>
修改TypeMapper.xml的insert元素,参数使用别名
<insert id="insertType" parameterType="Type">
................
</insert>
如果要定义别名的类型很多,也可以使用package元素进行定义,package的name属性指定类或接口所在的包的名称,此时类和接口的别名为它们的短名称(去掉包名后的名称)。
示例
<typeAliases>
<package name="com.cbmv.domain"/>
</typeAliases>
TypeMapper.xml的insert元素
<insert id="insertType" parameterType="Type">
................
</insert>
自定义类型转换器
MyBatis在对实体属性和表字段映射时会根据双方的类型自动进行类型转换,但是有些情况,比如实体中的日期为字符串,并且包含中文,而数据库中使用的是datetime。此时mybatis无法自动实现转换。需要自定义类型转换器
MyBatis的类型转换器需要实现org.apache.ibatis.type.TypeHandler接口,并实现如下几个方法
| public T getResult(ResultSet rs, String columnName) throws SQLException | 从结果集中获取值,并转换成需要的数据 rs : 数据集 columnName : 字段名 |
|---|---|
| public String getResult(ResultSet rs, int index) throws SQLException | 从结果集中获取值,并转换成需要的数据 rs : 数据集 index: 字段的位置,从1开始 |
| public String getResult(CallableStatement callback, int index) throws SQLException | 从数据库函数的返回值中获取值,并转换成需要的数 据 callback : 执行对象 index : 返回值在结果集中的位置 |
| public void setParameter(PreparedStatement ps, int index, String value, JdbcType type) throws SQLException | 把参数的值传递能JDBC执行对象 ps : 执行对象 index : 参数的位置,从1开始 value : 参数的值 type : 数据库中此字段的mybaits类型, |
示例
实现日期转换器,Type类中添加createTime属性,类型字符串,日期格式为“yyyy年MM月dd日”,在t_type表中定义create_time字段,类型为datetime。
- 定义类型转换器
创建包:com.creatorblue.handler,创建类DateConverStringHandler,实现TypeHandler接口
public class DateConverStringHandler implements TypeHandler<String> {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd");
//从数据库取出日期转成字符串
@Override
public String getResult(ResultSet arg0, String arg1) throws SQLException {
return sdf.format(arg0.getDate(arg1));
}
//从数据库取出日期转成字符串
@Override
public StringgetResult(ResultSet arg0, int arg1) throws SQLException {
// TODO Auto-generated method stub
return sdf.format(arg0.getDate(arg1));
}
//此方法不实现
@Override
public String getResult(CallableStatement arg0, int arg1) throws SQLException {
// TODO Auto-generated method stub
return null;
}
//把字符串转成数据库的日期
@Override
public void setParameter(PreparedStatement arg0, int arg1, String arg2, JdbcType arg3) throws SQLException {
arg0.setDate(arg1, new java.sql.Date(sdf.parse(arg2).getTime()));
}
}
类型转换器在使用时,分为全局和局部两种方式
全局转换器的使用
全局转换器是在Mybatis的配置文件中通过typeHandlers定义,当Mybatis在执行的过程中遇到全局转换所指定的类型时,会自动调用此转换器进行转换,MyBatis中内置的转换器都是全局转换器。
typeHandlers的子元素typeHandler用于定义每一个全局转换器,它的属性有:
| handler | 转换器类的全称,也可以是typeAliases元素定义的别名 |
| jdbcType | 字段的类型,必须是org.apache.ibatis.type.JdbcType枚举的值 |
| javaType | java类型 |
示例
<typeHandlers>
<typeHandler handler="com.creatorblue.handler.DateConverStringHandler" jdbcType="TIMESTAMP" javaType="java.lang.String"/>
</typeHandlers>
设置全局转换器后,不需要再做其它设置,当遇到所指定的类型时,MyBatis会自动调用此转换器进行转换。
局部转换器的使用
局部转换器是指在映射文件中特定的位置显示的使用类型转换器,如在resultMap元素的某一个字段映射上使用,以及在传参赋值时调用
字段映射
<result column="create_time" property="createTime" typeHandler="DateConverStringHandler"/>
赋值调用
<insert id="insertType" parameterType="type">
...............
#{createTime,typeHandler=DateConverStringHandler}
..........
</insert>
分页的实现
在MyBatis要实现分页查询非常简单,MyBatis提供了PageHelper接口用于实现分页查询,本教程使用5.1.2版本。
1、导包
引入pagehelper-5.1.2.jar包到项目中,此包可以在repo1.maven.org/maven2/com/…下找到
还需要引用关联的包jsqlparser-0.9.5.jar,此包可以在repo1.maven.org/maven2/com/…下找到
2、在Mabatis的配置文件中设置插件
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 数据库的类型 -->
<property name="helperDialect" value="mysql" />
<!-- 该参数默认为false -->
<!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
<!-- 和startPage中的pageNum效果一样 -->
<property name="offsetAsPageNum" value="true" />
<!-- 该参数默认为false -->
<!-- 设置为true时,使用RowBounds分页会进行count查询 -->
<property name="rowBoundsWithCount" value="true" />
<!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
<!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型) -->
<property name="pageSizeZero" value="true" />
<!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
<!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
<!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
<property name="reasonable" value="false" />
<!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
<!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 -->
<!-- 不理解该含义的前提下,不要随便复制该配置 -->
<property name="params" value="pageNum=start;pageSize=limit;" />
<!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
<property name="returnPageInfo" value="check" />
</plugin>
</plugins>
3、分页查询
查询方法的前一行调用PageHelper.startPage静态方法,常用带两个参数的重载,第一个参数为当前页码,第二个参数为每页显示数。
PageHelper.startPage(1, 10); //第一页显示10条记录
List<Type> list =session.getMapper(TypeMapper.class).selectAll(type);
4、处理分页结果
PageInfo<Type> page = new PageInfo<Type>(list);
System.out.println("总页数:" + page.getPages()
+ "\r总记录数:" + page.getTotal()
+ "\r当前页码" + page.getPageNum()
+ "\r每页显示数:" + page.getSize()
+ "\r数据集合中的元素:" + page.getList().get(0).getTypeCode());
使用接口和注解实现
接口实现
使用接口来实现调用可以更好的简化代码
SqlSession对象的getMapper方法可以获得定义的接口对象,此接口的名称与映射文件中的namespace相同,MyBatis能通过此名称方便的找到映射文件中对应的mapper元素
以下以配件商城的类型管理为例讲解使用接口如何实现Mybaits的调用
实现步骤
定义接口
在com.cashop.dao.mapper包中定义接口TypeMapper,并在接口中声明数据访问方法
public interface TypeMapper {
/**
* 查询总记录数
* @return
*/
int selectCount();
/**
* 分页查询所有分类
* @param list
* @return
*/
List<Type> selectAll(List<Object> list);
/**
* 根据ID查询分类
* @param typeId
* @return
*/
Type selectById(int typeId);
/**
* 保存分类
* @param type
* @return
*/
int saveType(Type type);
/**
* 修改分类
* @param type
* @return
*/
int updateType(Type type);
/**
* 删除分类
* @param typeId
* @return
*/
int deleteType(int typeId);
}
修改TypeMpper.xml文件
其中mapper元素的namespace属性值为接口的全称。
xml中sql定义的id与接口中的方法名相同,parameterType与接口方法的参数相同,resultType与接口方法的返回值类型一致
在测试类中添加方法TypeMapperTest
.........
TypeMapper mapper = session.getMapper(TypeMapper.class); //获取TypeMapper接口的实例
int count = mapper.deleteType(1);//删除ID为1的分类
........
指导练习
修改cbmv项目数据访问层,使用mybatis接口实现数据
添加事务控制
修改BaseDao
声明本地线程,用于存放当前线程的Mybaits查询会话,
//本地线程缓存,相当于一个map集合,key为线程的ID,value为指定的泛型对象
private static ThreadLocal<SqlSession> sessionCache = new ThreadLocal<>();
定义方法获取Mapper接口
protected <T> T getMapper(Class<T> cls){
SqlSession session = null;
if((session = sessionCache.get()) == null){
session = getSession();
sessionCache.set(session);
}
T mapper = session.getMapper(cls);
return mapper;
}
定义事务控制的方法
//事务提交方法
public static void commit(){
SqlSession session = null;
if((session = sessionCache.get()) != null){
session.commit();
}
}
//事务回滚方法
public static void rollback(){
SqlSession session = null;
if((session = sessionCache.get()) != null){
session.rollback();
}
}
定义会话关闭方法
public static void close(){
SqlSession session = null;
if((session = sessionCache.get()) != null){
session.close();
sessionCache.remove();
}
}
修改TypeDaoImpl类的方法
在类中定义TypeMapper的实现类对象引用
private TypeMapper typeMapper= getMapper(TypeMapper.class);
修改查询方法
/**
* 查询记录总数
* @return
*/
int count = 0;
try {
count = typeMapper.selectCountByType(t);
} finally{
close();
}
return count;
事务控制
只有增、删、改方法需要事务控制,查询不需要事务控制
以下是分别在Dao和Servcie中实现事务控制的方法
在Dao的实现类中实现事务控制
修改新增方法addType
int count = 0;
try{
count = typeMapper.insertType(t);
commit(); //提交事务
}catch(Exception e){
rollback();//异常时回滚事务;
throw new SQLException(e);
}finally{
close();
}
在service中实现事务控制
修改TypeDao的addType方法
int count = typeMapper.insertType(t);
修改TypeServiceImpl类的insertType方法
int count = 0;
try{
count = typeDao.typeMapper(t);
BaseDao.commit();
}catch(SQLException e){
BaseDao.rollback();
trhow e;
}finally{
BaseDao.close();
}
注意: 经过此改进后,可以发现Dao层已经没有太多的代码,基本都是一行代码实现功能,在许多的项目框架中都会去掉Dao层,直接在Service层中调用Mapper。也就是使用Mapper接口替代Dao层。
注解实现
MyBatis除了可以使用映射文件来实现语句的配置,还可以使用注解的实现
注解的实现是直接在接口的方法上定义语句的注解,如下所示
在MyBatis的配置文件中mapper再使用resource属性映射xml配置文件,而是使用class属性映射接口
常用注解
@select
@select注解对应xml映射文件中的select元素。用于定义select语句,语句中参数的获取与在xml文件中相同
示例
@Select("select count(*) from t_type")
int selectCount();
@Results和@Result
@Results
@Results注解对应xml映射文件中的resultMap元素,用于定义记录与对象的转换规则。
@Results的常用属性
id:相当于xml中resultMap元素的id属性,用于给映射集指定一个唯一的名称。
value:@Result注解的数组,用于定义每个字段与实体属性的映射
@Result
@Result注解对应xml映射文件中resultMap元素的子元素result,用于字段与属性之间的转换
@Result的常用属性
column:表的字段名
property:实体的属性名
id:是否是主键,如果是主键,则设置为true,默认为false
javaType:实体属性的数据类型
jdbcType:表中字段的数据类型,此类型为org.apache.ibatis.type.JdbcType枚举的值。
one:实现一对一的映射,类型为@One注解
many:实现一对多的映射,类型为@Many注解
typeHandler:自定义类型转换器
示例
@Select("select type_id,type_name,type_code,type_remark,create_time from"
+ " t_type limit #{list[0]},#{list[1]}")
@Results(id="typeMap",value={@Result(id=true,column="type_id",property="typeId")
,@Result(column="type_name",property="typeName")
,@Result(column="type_code",property="typeCode")
,@Result(column="type_remark",property="typeRemark")
,@Result(column="create_time",property="createTime",typeHandler=DateTypeHandler.class)})
List<Type> selectAll(List<Object> list);
@One
用于实现一对一及多对一的映射,相当于xml中的association元素
常用属性
fetchType:FetchType枚举类型,可选择EAGER和LAZY
select:关联的查询方法,可以是xml中select元素的id ,也可以是一个接口中的方法名,如果是本接口中的方法可以直接写方法名,如果是引用xml中的id或是其它接口中的方法,则需要写全称。
示例
@Select("")
@Results(id="proMap",value={@Result(column="type_id",
property="type",javaType=Type.class,
one=@One(select="com.cashop.dao.mapper.TypeAnnocationMapper.selectById"))})
List<Product> selectAll(Map<String,Object> map);
@Mony
用于一对多的映射,相同于xml中的collection元素
常用属性与@One相同
示例
@Result(column="type_id",property="proList",javaType=ArrayList.class
,many=@Many(select="com.cashop.dao.mapper.ProductAnnocationMapper.selectByTypeId"))
@ResultMap
用于引用已存在的Results注解或xml中resultMap元素的id,相当于xml中select元素的resultMap属性,如果引用的不是本接口中定义的@Results的id,则需要使用全称
@Select("select type_id,type_name,type_code,type_remark,create_time"
+ " from t_type where type_id=#{value}")
@ResultMap("typeMap")
Type selectById(int typeId);
@Insert
相当于xml中的insert元素,用于定义新增语句
@Insert("insert into t_type(type_name,type_code,type_remark,create_time)"
+ " values(#{typeName},#{typeCode},#{typeRemark},select now())")
int saveType(Type type);
@Update
相当于xml中的update元素,用于定义修改语句
@Update("update t_type set type_name = #{typeName},type_code = #{typeCode},"
+ "type_remark = #{typeRemark},where type_id = #{typeId} ")
int updateType(Type type);
@Delete
相当于xml中的delete元素,用于定义删除语句
@Delete("delete from t_type where type_id=#{value}")
int deleteType(int typeId);
注解扩展
- 使用注解方式的语句无法使用条件判断,可以使用Provider注解调用Java类来实现语句的拼接。
@selectProvider注解
属性
type:用于生成语句的类的类型
method:在类中用于生成语句的方法
示例
@SelectProvider(type=TypeMapperProvider.class,method="selectCount")
int selectCount();
public class TypeMapperProvider {
public String selectCount(){
return "select count(*) from t_type";
}
}
方法参数的规则
-
-
如果Mapper接口的方法没有参数,则Provider方法也没有参数
-
如果Mapper接口的方法是一个实体类型参数,则Provider方法的参数可以是此实体的类型
-
其它的参数类型在Provider中当应当使用Map集合做为参数
- 如果Mapper接口的方法是一个或一组基本类型参数,则应当使用@Param注解来指定参数名,Provider方法的参数为Map集合,key为@Param注解指定的名称
- 如果Mapper接口的方法是Map参数,则Provider方法的参数为Map
- 如果Mapper接口的方法是List参数,则Provider方法的参数为Map,key为list和collection
- 如果Mapper接口的方法是数组参数,则Provider方法的参数为Map,key为array
-
Provider类中方法总是返回一个String类型,即生成的sql语句
语句的生成方法常用的有三种
直接使用字符串拼接
return "select count(*) from t_type";
使用SQL对象的方法组合
return new SQL().SELECT("count(*)").FROM("t_type").WHERE("1=1").toString();
使用SQL对象子类对象
return new SQL(){
{
SELECT("count(*)");
FROM("t_type");
WHERE("1=1");
}
@InsertProvider、@UpdateProvider、@DeleteProvider和@SelectProvider的使用方法一致。
Mybatis与Spring整合
整合环境搭建
准备所需JAR包
要实现MyBatis与Spring的整合,很明显需要这两个框架的JAR包,但是只使用这两个框架中所提供的JAR包是不够的,还需要其他的JAR包来配合使用,整合时所需准备的JAR包具体如下。
1、Spring框架所需的JAR包
注意:核心容器依赖的commons-logging的JAR在MyBatis框架的lib包中已经包含!
2、MyBatis框架所需的JAR包
3、MyBatis与Spring整合的中间JAR
•mybatis-spring-1.3.1.jar
4、数据库驱动JAR(MySQL)
•mysql-connector-java-5.1.40-bin.jar
编写配置文件
1、创建项目(chapter10),引入JAR包
在Eclipse中,创建一个名称为chapter10的Web项目,将上一小节中所准备的全部JAR包添加到项目的lib目录中,并发布到类路径下。
2、编写db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root
jdbc.maxTotal=30
jdbc.maxIdle=10
jdbc.initialSize=5
3、编写Spring配置文件applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
...
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxTotal" value="${jdbc.maxTotal}" />
<property name="maxIdle" value="${jdbc.maxIdle}" />
<property name="initialSize" value="${jdbc.initialSize}" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
</beans>
4、编写MyBatis配置文件mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
…
<configuration>
<typeAliases>
<package name="com.creatorblue.po" />
</typeAliases>
<mappers>
...
</mappers>
</configuration>
5、引入log4j.properties
# Global logging configuration
log4j.rootLogger=ERROR, stdout
# MyBatis logging configuration...
log4j.logger.com.creatorblue=DEBUG
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
MapperFactoryBean是MyBatis-Spring团队提供的一个用于根据Mapper接口生成Mapper对象的类,该类在Spring配置文件中使用时可以配置以下参数:
mapperInterface:用于指定接口;
SqlSessionFactory:用于指定SqlSessionFactory;
SqlSessionTemplate:用于指定SqlSessionTemplate。如果与SqlSessionFactory同时设定,则只会启用SqlSessionTemplate。
虽然使用Mapper接口编程的方式很简单,但是在具体使用时还是需要遵循一些规范。
在实际的项目中,DAO层会包含很多接口,如果每一个接口都在Spring配置文件中配置,不但会增加工作量,还会使得Spring配置文件非常臃肿。为此,可以采用自动扫描的形式来配置MyBatis中的映射器——采用MapperScannerConfigurer类。
MapperScannerConfigurer类在Spring配置文件中可以配置以下属性:
basePackage:指定映射接口文件所在的包路径,当需要扫描多个包时可以使用分号或逗号作为分隔符。指定包路径后,会扫描该包及其子包中的所有文件。
annotationClass:指定了要扫描的注解名称,只有被注解标识的类才会被配置为映射器。
sqlSessionFactoryBeanName:指定在Spring中定义的SqlSessionFactory的Bean名称。
sqlSessionTemplateBeanName:指定在Spring中定义的SqlSessionTemplate的Bean名称。如果定义此属性,则sqlSessionFactoryBeanName将不起作用。
markerInterface:指定创建映射器的接口。
MapperScannerConfigurer的使用非常简单,只需要在Spring的配置文件中编写如下代码:
<!-- Mapper代理开发(基于MapperScannerConfigurer) -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.creatorblue.mapper" />
</bean>
通常情况下,MapperScannerConfigurer在使用时只需通过basePackage属性指定需要扫描的包即可,Spring会自动的通过包中的接口来生成映射器。这使得开发人员可以在编写很少代码的情况下,完成对映射器的配置,从而提高开发效率。