1、什么是MyBatis
MyBatis
原本是apache
基金会的一个开源项目iBatis
,2010年该项目由apache software foundation
迁移到google code
,并且改名为MyBatis
。2013年11月迁移到Github
。
MyBatis
是一款优秀的持久层 框架,它支持自定义 SQL
、存储过程以及高级映射。MyBatis
免除了几乎所有的 JDBC
代码以及设置参数和获取结果集的工作。MyBatis
可以通过简单的 XML
或注解来配置和映射原始类型、接口和 Java POJO
(Plain Ordinary Java Objects
,普通的Java
对象,又称:实体类 )为数据库中的记录。
附:持久化与持久层
(1)什么是持久化
持久化 就是将程序数据在**持久状态(数据库、磁盘文件等)和 瞬时状态(内存)**间转换的过程。即:把内存中的数据保存到可永久存储的存储设备中(如数据库、磁盘文件等)。
持久化的主要应用就是:将内存中的对象存储在数据库中,或者存储在磁盘文件(XML文件等)中。
JDBC
、MyBatis
等就是一种将数据持久化到数据库的机制。
文件IO
也是一种持久化机制。
(2)为什么需要持久化
内存的数据断电即丢失,实际项目中为了防止数据丢失,需要对数据进行持久化。
与硬盘相比,内存存储容量有限,且价格昂贵。
(3)什么是持久层
持久层 是完成持久化工作的代码块,又称Dao(Date Access Object)
层。
2、为什么需要MyBatis
传统的JDBC
操作数据库过程复杂,代码重复率高。
使用MyBatis
可以极大地简化数据库操作流程。
MyBatis
小巧灵活,简单易学,没有任何第三方依赖。
MyBatis
将SQL
与程序代码分离(SQL
写在xml
文件中),将业务逻辑和数据库访问逻辑分离,解除了SQL
与程序代码之间的耦合,使系统的结构更清晰,便于后期维护。
目前使用的人多。
注:技术没有高低之分,只有使用这个技术的人有高低之分。
3、如何获取MyBatis
**方式一:**从Maven
仓库下载。
如果使用Maven
来构建项目,则需将下面的依赖代码添加到pom.xml
文件中:
1 2 3 4 5 <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.2</version > </dependency >
**注:**我们这里使用的MyBatis
版本为3.5.2
。
**方式二:**从Github
下载源码或jar
包。
**注:**若https://github.com/拒绝了我们的连接请求,则是因为`Github`的域名被`DNS`污染了。此时,我们只需先通过[`DNS`查询](https://tool.chinaz.com/dns/)获取`Github`网站的`IP`,然后再修改`C:\Windows\System32\drivers\etc`路径下的`hosts`文件,在`hosts`文件中添加`Github`网站的`IP`地址即可访问`Github`网站。如下图所示:
4、第一个MyBatis程序
4.1、创建数据库
使用SQL
创建一个名称为mybatis
的数据库,在其中创建一个user
表,并向其中插入测试数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 CREATE DATABASE `mybatis`; USE `mybatis`; CREATE TABLE `user `( `id` INT (20 ) NOT NULL , `name` VARCHAR (30 ) DEFAULT NULL , `pwd` VARCHAR (30 ) DEFAULT NULL , PRIMARY KEY(`id`) )ENGINE= INNODB DEFAULT CHARSET= utf8; INSERT INTO `user `(`id`,`name`,`pwd`) VALUES (1 ,'阿汤' ,'123456' ), (2 ,'张三' ,'123abc' ), (3 ,'李四' ,'888888' )
创建的数据库如下图所示:
4.2、创建普通的Maven项目
(1)在IDEA中创建一个普通的Maven项目。(具体步骤参考《Maven详解 》),项目名称为Mybatis-Study
。
(2)删除系统自动生成的src
目录,所得到的空项目就是我们的Maven父项目 ,我们可以在其中创建多个子项目(即:Module
)。
(3)在父项目的pom.xml
文件中导入父/子工程需要的Maven依赖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <dependencies > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.47</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.2</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.11</version > <scope > test</scope > </dependency > </dependencies >
(4)在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目 ,项目名称为mybatis-01
。
(5)填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
4.3、创建MyBatis核心配置文件
在Maven子项目的src/main/resources
目录下新建一个名称为mybatis-config.xml
的配置文件。该配置文件中包含了对 MyBatis
系统的核心设置,包括获取数据库连接实例的数据源 (DataSource
)、决定事务作用域和控制方式的事务管理器 (TransactionManager
)、注册所有的XML映射器配置文件 等。
mybatis-config.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration > <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="driver" value ="com.mysql.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost:3306/mybatis?useUnicode=true& characterEncoding=utf8& useSSL=true" /> <property name ="username" value ="root" /> <property name ="password" value ="123456" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="com/atangbiji/dao/UserMapper.xml" /> </mappers > </configuration >
注:
数据源连接信息与使用IDEA连接数据库相同(参见《使用IDEA连接数据库 》)。
每一个XXXMapper.xml
映射器配置文件都需要在MyBatis
的核心配置文件中注册。
在XML文件中,我们可以规定所有标签的先后顺序。MyBatis
中规定了核心配置文件mybatis-config.xml
中各标签的顺序(如下图所示),我们必须严格遵守。
4.4、封装MyBatis工具类
在子项目的java
目录下新建一个com.atangbiji.utils
包(用于存放我们自己封装的MyBatis
工具类),并在该包下新建一个MyBatisUtils
类,用于获取SqlSession
对象。
MyBatisUtils.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.atangbiji.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;public class MyBatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { String resource = "mybatis-config.xml" ; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder ().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession () { return sqlSessionFactory.openSession(); } public static SqlSession getSqlSession (boolean autoCommit) { return sqlSessionFactory.openSession(autoCommit); } }
注:
SqlSessionFactory
类是 MyBatis
应用的核心类。
SqlSessionFactory
的实例可以通过 SqlSessionFactoryBuilder
类创建,且SqlSessionFactoryBuilder
可以从mybatis-config.xml
核心配置文件中构建出SqlSessionFactory
实例。
我们可以从SqlSessionFactory
中获取SqlSession
对象。
SqlSession
类提供了在数据库执行SQL
命令所需的所有方法,我们可以通过SqlSession
实例来直接执行已映射的SQL
语句。
虽然我们可以将事务设置成自动提交,但实际项目中为了数据的安全,建议手动提交事务。
4.5、创建实体类
在子项目的java
目录下新建一个com.atangbiji.pojo
包(用于存放我们自己编写的实体类(pojo
)),并在该包下新建一个User
类,用于实现关系型数据库和业务实体间的映射。
User.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.atangbiji.pojo;public class User { private int id; private String name; private String pwd; public User () { } public User (int id, String name, String pwd) { this .id = id; this .name = name; this .pwd = pwd; } 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 String getPwd () { return pwd; } public void setPwd (String pwd) { this .pwd = pwd; } @Override public String toString () { return "User{" + "id=" + id + ", name='" + name + '\'' + ", pwd='" + pwd + '\'' + '}' ; } }
4.6、创建映射器接口(重点)
在子项目的java
目录下新建一个com.atangbiji.dao
包(用于存放我们自己编写的持久层代码),并在该包下新建一个UserMapper
映射器接口,然后再向其中添加映射器接口函数。
UserMapper.java
文件:
1 2 3 4 5 6 7 8 9 package com.atangbiji.dao;import com.atangbiji.pojo.User;import java.util.List;public interface UserMapper { List<User> getUserList () ; }
注:映射器接口中的 接口函数用于映射XML
映射器配置文件中对应标签中的SQL
语句 。
4.7、创建XML映射器配置文件(重点)
在子项目的com.atangbiji.dao
包下新建一个映射器配置文件UserMapper.xml
,并在其中使用标签实现SQL
语句与映射器接口函数的映射 。
UserMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.UserMapper" > <select id ="getUserList" resultType ="com.atangbiji.pojo.User" > select * from mybatis.user </select > </mapper >
与JDBC
不同的是:MyBatis
项目中不再创建映射器接口的实现类 了,而是用一个XML
映射器配置文件取代了JDBC
项目中的接口实现类 ,进而免除了几乎所有的 JDBC
代码。我们只需在该映射器配置文件中配置标签属性:
namespace
:对应映射器接口的全限定名。
id
:对应映射器接口中的方法名。
resultType
:SQL
语句执行结果 所对应实体类(pojo)的全限定名。
注:
XML映射器配置文件通过标签的方式实现SQL
语句与映射器接口函数的映射 。
XML映射器配置文件中的一个标签对应一条SQL
语句(即:一个业务)。
XML映射器配置文件中的标签与映射器接口中的接口函数一一对应。
若传入参数(parameterType
)为简单类型(包括:基本数据类型、String、Date等),则#{}预编译占位符中的形参与对应映射器接口函数的形参一一对应。
若传入参数(parameterType
)为实体类(pojo)类型,则#{}预编译占位符中的形参与该实体类的成员变量一一对应。
若传入参数(parameterType
)为Map类型,则#{}预编译占位符中的形参与该Map中键名(key)一一对应。
命名空间的作用有两个:
一是利用更长的全限定名来将不同的SQL
语句隔离开来;
二是实现接口绑定。
一个 XML 映射器配置文件中,可以定义无数个SQL
映射语句。
为了增加代码的可读性,建议将XML映射器配置文件与映射器接口文件放在同一(dao
)目录下。
4.8、测试
(1)在子项目的src/test/java
目录下,新建一个com.atangbiji.dao
包,并在该包下新建一个UserMapperTest
,用于MyBatis
功能测试。
UserMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.atangbiji.dao;import com.atangbiji.pojo.User;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.List;public class UserMapperTest { @Test public void test () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.getUserList(); for (User user : userList) { System.out.println(user); } } } }
注1:
1 2 3 try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ }
是带资源的try
语句 (try-with-resource
)。try
块退出时,会自动调用sqlSession.close()
方法,关闭资源。
**注2:**由于Maven的“约定大于配置 ”思想,我们的映射器配置文件*.xml
写在了项目中的Java
目录下,因此出现无法被导出或者无法生效的问题。该问题的解决方法是:在父项目(或子项目)的pom.xml
的<build>……</build>
中配置resources
,修改Maven约定的过滤条件,来防止我们的资源导出失败。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <build > <resources > <resource > <directory > src/main/resources</directory > <includes > <include > **/*.properties</include > <include > **/*.xml</include > </includes > <filtering > false</filtering > </resource > <resource > <directory > src/main/java</directory > <includes > <include > **/*.properties</include > <include > **/*.xml</include > </includes > <filtering > false</filtering > </resource > </resources > </build >
(2)重新加载Maven项目,运行测试程序,执行结果如下图所示:
5、实现增删改查
在MyBatis
中,我们可以通过配置XML
映射器配置文件 的方式实现数据的增删改查。
注: XML
映射器配置文件中的一个标签既对应一个SQL
语句,又对应映射器接口中一个方法(类似于JDBC
项目中接口实现类中的一个方法)。
5.1、映射插入语句——insert标签
要实现插入功能,只需修改如下文件:
(1)添加映射器接口函数。
UserMapper.java
文件:
1 2 3 4 5 6 7 8 9 package com.atangbiji.dao;import com.atangbiji.pojo.User;import java.util.List;public interface UserMapper { void addUser (User user) ; }
(2)编写对应的XML映射器配置文件。
UserMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.UserMapper" > <insert id ="addUser" parameterType ="com.atangbiji.pojo.User" > insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd}) </insert > </mapper >
注:
若传入参数(parameterType
)为简单类型(包括:基本数据类型、String、Date等),则#{}预编译占位符中的形参与对应映射器接口函数的形参一一对应。
若传入参数(parameterType
)为实体类(pojo)类型,则#{}预编译占位符中的形参与该实体类的成员变量一一对应。
若传入参数(parameterType
)为Map类型,则#{}预编译占位符中的形参与该Map中键名(key)一一对应。
(3)测试。
UserMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.atangbiji.dao;import com.atangbiji.pojo.User;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.List;public class UserMapperTest { @Test public void insertTest () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.addUser(new User (4 ,"王二" ,"111111" )); sqlSession.commit(); } } }
运行测试函数insertTest
,刷新数据库表发现数据插入成功,如下图所示:
注: MyBatis
中增加、删除和修改数据必须要提交事务 ,否则数据无法持久化。
**附:**若要在IDEA
中显示SQL
提示,需要在设置中注入SQL
。如下图所示:
5.2、映射删除语句——delete标签
同样地,要实现删除功能,只需修改如下文件:
(1)添加映射器接口函数。
UserMapper.java
文件:
1 2 3 4 5 6 7 8 9 package com.atangbiji.dao;import com.atangbiji.pojo.User;import java.util.List;public interface UserMapper { void deleteUser (int id) ; }
(2)编写对应的XML映射器配置文件。
UserMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.UserMapper" > <delete id ="deleteUser" parameterType ="int" > delete from mybatis.user where id = #{id} </delete > </mapper >
注:
若传入参数(parameterType
)为简单类型(包括:基本数据类型、String、Date等),则#{}预编译占位符中的形参与对应映射器接口函数的形参一一对应。
若传入参数(parameterType
)为实体类(pojo)类型,则#{}预编译占位符中的形参与该实体类的成员变量一一对应。
若传入参数(parameterType
)为Map类型,则#{}预编译占位符中的形参与该Map中键名(key)一一对应。
(3)测试。
UserMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.atangbiji.dao;import com.atangbiji.pojo.User;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.List;public class UserMapperTest { @Test public void deleteTest () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.deleteUser(4 ); sqlSession.commit(); } } }
运行测试函数deleteTest
,刷新数据库表发现数据删除成功,如下图所示:
注: MyBatis
中增加、删除和修改数据必须要提交事务 ,否则数据无法持久化。
5.3、映射更新语句——update标签
同样地,要实现更新功能,只需修改如下文件:
(1)添加映射器接口函数。
UserMapper.java
文件:
1 2 3 4 5 6 7 8 9 package com.atangbiji.dao;import com.atangbiji.pojo.User;import java.util.List;public interface UserMapper { void updateUser (User user) ; }
(2)编写对应的XML映射器配置文件。
UserMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.UserMapper" > <update id ="updateUser" parameterType ="com.atangbiji.pojo.User" > update mybatis.user set name = #{name}, pwd = #{pwd} where id = #{id} </update > </mapper >
注:
若传入参数(parameterType
)为简单类型(包括:基本数据类型、String、Date等),则#{}预编译占位符中的形参与对应映射器接口函数的形参一一对应。
若传入参数(parameterType
)为实体类(pojo)类型,则#{}预编译占位符中的形参与该实体类的成员变量一一对应。
若传入参数(parameterType
)为Map类型,则#{}预编译占位符中的形参与该Map中键名(key)一一对应。
(3)测试。
UserMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.atangbiji.dao;import com.atangbiji.pojo.User;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.List;public class UserMapperTest { @Test public void updateTest () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.updateUser(new User (2 ,"张三丰" ,"aaaaaa" )); sqlSession.commit(); } } }
运行测试函数updateTest
,刷新数据库表发现数据修改成功,如下图所示:
注: MyBatis
中增加、删除和修改数据必须要提交事务 ,否则数据无法持久化。
附: MyBatis
中,数据变更语句 insert
、update
和delete
的实现非常接近,我们可以配置很多属性来配置每条SQL
语句的行为细节。 insert
、update
和delete
标签的属性如下表所示:
属性
描述
备注
id
在命名空间中唯一的标识符,对应映射器接口中的方法名 。
parameterType
将要传入该SQL
语句的参数的类型。传入参数类型 有以下三种:(1)简单类型(包括:基本数据类型、String、Date等);(2)实体类(pojo)类型;(3)Map类型。
该属性是可选的,因为MyBatis
可以通过类型处理器(TypeHandler
)推断出具体传入语句的参数,默认值:unset(未设置)。
flushCache
若将其设置为 true ,则只要该语句被调用,都会导致本地缓存和二级缓存被清空。
默认值:true(对 insert、update 和 delete 语句)。
timeout
在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。
默认值:unset(未设置),依赖数据库驱动。
statementType
可选 STATEMENT
,PREPARED
或 CALLABLE
。会让 MyBatis
分别使用Statement
,PreparedStatement
或 CallableStatement
。
默认值:PREPARED。
useGeneratedKeys
(仅适用于insert
和update
)这会令 MyBatis
使用 JDBC
的getGeneratedKeys
方法来取出由数据库内部生成的主键(如:像MySQL
和SQL Server
这样的关系型数据库管理系统的自动递增字段)。
默认值:false
keyProperty
(仅适用于insert
和update
)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys
的返回值或insert
语句的selectKey
子元素设置它的值。如果生成列不止一个,可以用逗号分隔多个属性名称。
默认值:unset(未设置)
keyColumn
(仅适用于insert
和update
)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL
)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
databaseId
如果配置了数据库厂商标识(databaseIdProvider
),MyBatis
会加载所有不带 databaseId
或匹配当前 databaseId
的语句;如果带和不带的语句都有,则不带的会被忽略。
5.4、映射查询语句——select标签
select
标签是MyBatis
中最常用的标签之一,一个简单查询的select
标签是非常简单的。同样地,要实现查询功能,只需修改如下文件:
(1)添加映射器接口函数。
UserMapper.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.atangbiji.dao;import com.atangbiji.pojo.User;import java.util.List;public interface UserMapper { List<User> getUserList () ; User getUserById (int id) ; List<User> getUserLike (String value) ; }
(2)编写对应的XML映射器配置文件。
UserMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.UserMapper" > <select id ="getUserList" resultType ="com.atangbiji.pojo.User" > select * from mybatis.user </select > <select id ="getUserById" parameterType ="int" resultType ="com.atangbiji.pojo.User" > select * from mybatis.user where id = #{id} </select > <select id ="getUserLike" resultType ="com.atangbiji.pojo.User" > select * from mybatis.user where name like #{value} </select > </mapper >
注:
若传入参数(parameterType
)为简单类型(包括:基本数据类型、String、Date等),则#{}预编译占位符中的形参与对应映射器接口函数的形参一一对应。
若传入参数(parameterType
)为实体类(pojo)类型,则#{}预编译占位符中的形参与该实体类的成员变量一一对应。
若传入参数(parameterType
)为Map类型,则#{}预编译占位符中的形参与该Map中键名(key)一一对应。
(3)测试。
UserMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package com.atangbiji.dao;import com.atangbiji.pojo.User;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.List;public class UserMapperTest { @Test public void selectTest1 () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.getUserList(); System.out.println("查询全部用户,查询结果为:" ); for (User user : userList) { System.out.println(user); } } } @Test public void selectTest2 () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.getUserById(1 ); System.out.println("根据id查询用户,查询结果为:" ); System.out.println(user); } } @Test public void selectLikeTest () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.getUserLike("%张%" ); System.out.println("模糊查询结果为:" ); for (User user : userList) { System.out.println(user); } } } }
运行测试函数selectTest1
,执行结果如下图所示:
运行测试程序selectTest2
,执行结果如下图所示:
运行测试程序selectLikeTest
,执行结果如下图所示:
附: select
标签允许你配置很多属性来配置每条查询语句的行为细节。select
标签的属性如下表所示:
属性
描述
备注
id
在命名空间中唯一的标识符,对应映射器接口中的方法名 。
parameterType
将要传入该SQL
语句的参数的类型。传入参数类型 有以下三种:(1)简单类型(包括:基本数据类型、String、Date等);(2)实体类(pojo)类型;(3)Map类型。
该属性是可选的,因为MyBatis
可以通过类型处理器(TypeHandler
)推断出具体传入语句的参数,默认值:unset(未设置)。
resultType
SQL
语句返回值的类型 。要么是基本数据类型;要么是SQL
语句执行结果所对应实体类(pojo)的全限定名或别名。
若返回的是集合,则应该设置为集合包含的类型,而不是集合本身的类型。 resultType
与resultMap
两者只能同时使用一个。
resultMap
对外部 resultMap
的命名引用。
结果映射是MyBatis
最强大的特性 ,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType
和 resultMap
之间只能同时使用一个。
flushCache
若将其设置为 true ,则只要该语句被调用,都会导致本地缓存和二级缓存被清空。
默认值:false(对 select语句)。
useCache
若将其设置为 true ,则将会导致本条语句的结果被二级缓存缓存起来。
默认值:true(对 select语句)。
timeout
在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。
默认值:unset(未设置),依赖数据库驱动。
fetchSize
这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。
默认值:unset(未设置),依赖数据库驱动。
statementType
可选 STATEMENT
,PREPARED
或 CALLABLE
。会让 MyBatis
分别使用Statement
,PreparedStatement
或 CallableStatement
。
默认值:PREPARED。
resultSetType
FORWARD_ONLY
,SCROLL_SENSITIVE
,SCROLL_INSENSITIVE
或DEFAULT
(等价于unset
) 中的一个。
默认值:unset(未设置),依赖数据库驱动。
databaseId
如果配置了数据库厂商标识(databaseIdProvider
),MyBatis
会加载所有不带 databaseId
或匹配当前 databaseId
的语句;如果带和不带的语句都有,则不带的会被忽略。
resultOrdered
该设置仅针对嵌套结果select
语句。若为 true,则假设结果集以正确顺序(排序后)执行映射,当返回新的主结果行时,将不再发生对以前结果行的引用。 这样可以减少内存消耗。
默认值:false
。
resultSets
仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。
6、MyBatis核心配置文件解析
(1)在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目 ,项目名称为mybatis-02
。
(2)填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
(3)在该Maven子项目的src/main/resources
目录下新建MyBatis
的核心配置文件mybatis-config.xml
。
(4)在该子项目的java
目录下新建一个com.atangbiji.utils
包(用于存放我们自己封装的MyBatis
工具类),并在该包下新建一个MyBatisUtils
类,用于获取SqlSession
对象。
(5)在该子项目的java
目录下新建一个com.atangbiji.pojo
包(用于存放我们自己编写的实体类(pojo
)),并在该包下新建一个User
类,用于实现关系型数据库和业务实体间的映射。
(6)在该子项目的java
目录下新建一个com.atangbiji.dao
包(用于存放我们自己编写的持久层代码),并在该包下新建一个UserMapper
映射器接口。
(7)在该子项目的com.atangbiji.dao
包下新建一个映射器配置文件UserMapper.xml
,并在其中使用标签实现SQL
语句与映射器接口函数的映射。
(8)在该子项目的src/test/java
目录下,新建一个com.atangbiji.dao
包,并在该包下新建一个UserMapperTest
,用于MyBatis
功能测试。
6.1、属性(properties)配置(重点)
我们可以通过properties
属性来实现“引入外部配置文件 ”功能。具体实现步骤如下:
(1)在该子项目的src/main/resources
目录下,新建一个db.properties
配置文件,用于配置数据库。
db.properties
文件:
1 2 3 4 driver = com.mysql.jdbc.Driver url = jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true username = root password = 123456
(2)在MyBatis
核心配置文件中引入数据库配置文件。
mybatis-config.xml
文件:
1 <properties resource ="db.properties" />
注:
在MyBatis
核心配置文件中引入数据库配置文件后,我们便可以通过字符串替换符号 ${}
引用外部配置文件中的key
了。
附:同名属性优先级问题
如果一个属性在多个地方进行了配置,那么,MyBatis
将按照下面的顺序进行加载:
首先读取在properties
标签体内指定的属性(property
)。
然后根据properties
标签中的resource
属性读取类路径下外部配置文件,或根据url
属性指定的路径读取外部配置文件,并覆盖之前读取过的同名属性。
最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
因此,通过方法参数传递的属性具有最高优先级,resource
或url
属性中指定的外部配置文件优先级次之, properties
标签中指定的属性优先级最低。
6.2、设置(settings)配置
MyBatis
的可设置项众多,详解官方文档。我们只需了解如下几个即可:
cacheEnabled
:全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。(默认值:true
)
lazyLoadingEnabled
:懒加载 的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType
属性来覆盖该项的开关状态。(默认值:false
)
useGeneratedKeys
:允许JDBC
支持自动生成主键,需要数据库驱动支持。如果设置为true
,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。(默认值:false
)
mapUnderscoreToCamelCase
:是否开启驼峰命名 自动映射,即从经典数据库列名A_COLUMN
映射到经典 Java属性名aColumn
。(默认值:false
)
logImpl
:指定MyBatis
所用日志 的具体实现,未指定时将自动查找。(默认值:unset
)
一个配置完整的settings
标签示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <settings > <setting name ="cacheEnabled" value ="true" /> <setting name ="lazyLoadingEnabled" value ="true" /> <setting name ="aggressiveLazyLoading" value ="true" /> <setting name ="multipleResultSetsEnabled" value ="true" /> <setting name ="useColumnLabel" value ="true" /> <setting name ="useGeneratedKeys" value ="false" /> <setting name ="autoMappingBehavior" value ="PARTIAL" /> <setting name ="autoMappingUnknownColumnBehavior" value ="WARNING" /> <setting name ="defaultExecutorType" value ="SIMPLE" /> <setting name ="defaultStatementTimeout" value ="25" /> <setting name ="defaultFetchSize" value ="100" /> <setting name ="safeRowBoundsEnabled" value ="false" /> <setting name ="safeResultHandlerEnabled" value ="true" /> <setting name ="mapUnderscoreToCamelCase" value ="false" /> <setting name ="localCacheScope" value ="SESSION" /> <setting name ="jdbcTypeForNull" value ="OTHER" /> <setting name ="lazyLoadTriggerMethods" value ="equals,clone,hashCode,toString" /> <setting name ="defaultScriptingLanguage" value ="org.apache.ibatis.scripting.xmltags.XMLLanguageDriver" /> <setting name ="defaultEnumTypeHandler" value ="org.apache.ibatis.type.EnumTypeHandler" /> <setting name ="callSettersOnNulls" value ="false" /> <setting name ="returnInstanceForEmptyRow" value ="false" /> <setting name ="logPrefix" value ="exampleLogPreFix_" /> <setting name ="logImpl" value ="SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING" /> <setting name ="proxyFactory" value ="CGLIB | JAVASSIST" /> <setting name ="vfsImpl" value ="org.mybatis.example.YourselfVfsImpl" /> <setting name ="useActualParamName" value ="true" /> <setting name ="configurationFactory" value ="org.mybatis.example.ConfigurationFactory" /> </settings >
6.3、类型别名(typeAliases)配置(重点)
类型别名(typeAliases
)可为 Java
类型设置一个缩写名字,它存在的意义在于降低冗余的全限定类名书写 。它仅用于 XML 配置。
(1)自定义别名
方式一:自定义实体类的别名
我们可以在MyBatis
核心配置文件中为我们自己编写的实体类(pojo
)的全限定类名自定义别名。如:
mybatis-config.xml
文件:
1 2 3 4 <typeAliases > <typeAlias type ="com.atangbiji.pojo.User" alias ="User" /> </typeAliases >
方式二:指定包的名称
我们也可以在typeAliases
中指定一个包名,MyBatis
会在该包名下自动搜索需要的实体类( Java Bean/pojo
)。如:
1 2 3 4 <typeAliases > <package name ="com.atangbiji.pojo" /> </typeAliases >
此时, com.atangbiji.pojo
包中的每一个Java Bean
,在没有注解的情况下,会使用Bean
的**的非限定类名(首字母不区分大小写)**来作为它的别名, 如: com.atangbiji.pojo.User
实体类的别名为 user
。若有注解,则别名为其注解值。如:
User.java
文件:
1 2 3 4 5 @Alias("user") public class User { ... }
注:
当实体类比较少时,建议使用方式一。
当实体类十分多时,建议使用方式二。
方式一可以自定义别名;方式二在不使用注解的情况下不能自定义别名,方式二若要实现自定义别名,则需要在实体类前再添加注解。
别名配置完成后,我们便可以将任何使用全限定类名的地方替换为对应的别名了。如:
UserMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.UserMapper" > <select id ="getUserList" resultType ="User" > select * from mybatis.user </select > <select id ="getUserById" parameterType ="int" resultType ="User" > select * from mybatis.user where id = #{id} </select > </mapper >
(2)默认别名
MyBatis
中默认定义一些常见的 Java 类型的类型别名。它们都是不区分大小写 的。如下表所示:
别名
映射的类型
_byte
byte
_char (since 3.5.10)
char
_character (since 3.5.10)
char
_long
long
_short
short
_int
int
_integer
int
_double
double
_float
float
_boolean
boolean
string
String
byte
Byte
char (since 3.5.10)
Character
character (since 3.5.10)
Character
long
Long
short
Short
int
Integer
integer
Integer
double
Double
float
Float
boolean
Boolean
date
Date
decimal
BigDecimal
bigdecimal
BigDecimal
biginteger
BigInteger
object
Object
date[]
Date[]
decimal[]
BigDecimal[]
bigdecimal[]
BigDecimal[]
biginteger[]
BigInteger[]
object[]
Object[]
map
Map
hashmap
HashMap
list
List
arraylist
ArrayList
collection
Collection
iterator
Iterator
6.4、环境(environments)配置
MyBatis
可以配置多套环境,如:开发、测试和生产环境。
尽管MyBatis
可以配置多套环境,但我们只能通过default
属性选择其中的一套作为当前环境(如:default="development"
)。
MyBatis
中有两种类型的事务管理器(即:type="[JDBC|MANAGED]"
),一般默认为:JDBC
。
注: Spring + MyBatis
项目中没有必要配置事务管理器,因为 Spring
模块会使用自带的管理器来覆盖前面的配置。
MyBatis
中有三种数据源(dataSource
)类型(即:type="[UNPOOLED|POOLED|JNDI]"
),一般默认为:POOLED
。
UNPOOLED
:不使用数据库连接池。
POOLED
:使用数据库连接池 。
JNDI
:该数据源实现是为了能在如EJB
或应用服务器这类容器中使用,目前已经很少使用。
示例:
mybatis-config.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration > <properties resource ="db.properties" /> <environments default ="development" > <environment id ="development" > <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 > <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 > </configuration >
注:在MyBatis
核心配置文件中引入数据库配置文件后,我们便可以通过 字符串替换符号 ${}
引用外部配置文件中的key
了。
附:#{}
与${}
的区别
注: #{}
可以防止SQL
注入,但${}
不能防止SQL
注入。
6.5、映射器(mappers)配置(重点)
既然我们已经在XXXMapper.xml
映射器配置文件中定义了映射器(mapper),如果要执行各映射器中的SQL
语句,就需要告诉 MyBatis
去哪里找到相应的XML映射器配置文件,进而找到相应的SQL
语句。
我们可以通过如下几种方式配置映射器:
方式一:使用资源路径【推荐使用】
1 2 3 4 <mappers > <mapper resource ="com/atangbiji/dao/UserMapper.xml" /> </mappers >
方式二:使用映射器接口类的完全限定类名
1 2 3 <mappers > <mapper class ="com.atangbiji.dao.UserMapper" /> </mappers >
**注1:**使用方式二配置映射器需要满足以下两个条件:
映射器接口文件和XML映射器配置文件必须同名 。
映射器接口文件和XML映射器配置文件必须在同一个包下 。
**注2:**通过方式二,如果想把XML映射器配置文件移到resources
目录下,只需要在resources
目录下创建和映射器接口文件所在路径相同的包即可。因为这样编译后,class
文件就会和xml
文件在同一包下,而MyBatis
的资源路径(resource
)是通过classpath
来找文件的。
方式三:指定包的名称
1 2 3 4 <mappers > <package name ="com.atangbiji.dao" /> </mappers >
**注1:**与方式二一样,使用方式三配置映射器也需要满足以下两个条件:
映射器接口文件和XML映射器配置文件必须同名 。
映射器接口文件和XML映射器配置文件必须在同一个包下 。
**注2:**通过方式三,如果想把XML映射器配置文件移到resources
目录下,只需要在resources
目录下创建和映射器接口文件所在路径相同的包即可。因为这样编译后,class
文件就会和xml
文件在同一包下,而MyBatis
的资源路径(resource
)是通过classpath
来找文件的。
6.6、其它配置(了解)
MyBatis
的其他配置包括:类型处理器(typeHandlers
)、对象工厂(objectFactory
)、插件(plugins
)和数据库厂商标识(databaseIdProvider
)。
注:常用的MyBatis
插件有:MyBatis-Plus
、通用Mapper
等。
7、作用域和生命周期
理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
由于SqlSessionFactory
类是 MyBatis
应用的核心类。SqlSessionFactory
的实例可以通过 SqlSessionFactoryBuilder
类创建,且SqlSessionFactoryBuilder
可以从mybatis-config.xml
核心配置文件中构建出SqlSessionFactory
实例。我们可以从SqlSessionFactory
中获取SqlSession
对象。SqlSession
类提供了在数据库执行SQL
命令所需的所有方法,我们可以通过SqlSession
实例来直接执行已映射的SQL
语句。
Mybatis
的执行过程如下图所示:
SqlSessionFactoryBuilder
的作用在于创建SqlSessionFactory
,创建成功后,SqlSessionFactoryBuilder
就失去了作用,所以它只能存在于创建SqlSessionFactory
的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder
实例的最佳作用域是方法作用域 (也就是局部方法变量)。
SqlSessionFactory
可以被认为是一个数据库连接池,它的作用是创建SqlSession
接口对象。因为MyBatis
的本质就是 Java
对数据库的操作,所以SqlSessionFactory
的生命周期存在于整个MyBatis
的应用之中,所以一旦创建了SqlSessionFactory
,就要长期保存它,直至不再使用MyBatis
应用为止,所以可以认为SqlSessionFactory
的生命周期就等同于MyBatis
应用的生命周期。
由于SqlSessionFactory
是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个SqlSessionFactory
,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。
因此在一般的应用中我们往往希望SqlSessionFactory
作为一个单例,让它在应用中被共享。所以说**SqlSessionFactory
的最佳作用域是应用作用域。**
如果说SqlSessionFactory
相当于数据库连接池,那么SqlSession
就相当于一个数据库连接(Connection
对象)。一个SqlSessionFactory
可以创建多个SqlSession
对象(如下图所示)。你可以在一个事务里面执行多条SQL
,然后通过它的commit
、rollback
等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给SqlSessionFactory
,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以建议使用带资源的try语句 来保证其正确关闭。所以SqlSession
的最佳的作用域是请求或方法作用域。
注:
1 2 3 try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ }
是带资源的try
语句(try-with-resource
)。try
块退出时,会自动调用sqlSession.close()
方法,关闭资源。
附:Mybatis
详细的执行流程(分析源码)
8、传入参数类型(重点)
XML映射器配置文件中,SQL
语句传入参数的类型(parameterType
)有以下三种:(1)简单类型(包括:基本数据类型、String、Date等);(2)实体类(pojo)类型;(3)Map类型。
若传入参数(parameterType
)为简单类型,则#{}预编译占位符中的形参与对应映射器接口函数的形参一一对应。
若传入参数(parameterType
)为实体类(pojo)类型,则#{}预编译占位符中的形参与该实体类的成员变量一一对应。
若传入参数(parameterType
)为Map类型,则#{}预编译占位符中的形参与该Map中键名(key)一一对应。
8.1、简单类型
简单类型主要包括:基本数据类型、String
、Date
等。
示例:在SQL
语句中传入简单类型,实现删除用户的功能,只需修改如下文件:
(1)UserMapper.java
文件:
1 2 3 4 5 6 7 8 9 10 11 package com.atangbiji.dao;import com.atangbiji.pojo.User;import java.util.List;public interface UserMapper { void deleteUser (int id) ; User getUserByName (String name) ; }
(2)UserMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.UserMapper" > <delete id ="deleteUser" parameterType ="int" > delete from mybatis.user where id = #{id} </delete > <select id ="getUserByName" parameterType ="String" resultType ="com.atangbiji.pojo.User" > select * from mybatis.user where name = #{name} </select > </mapper >
(3)UserMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.atangbiji.dao;import com.atangbiji.pojo.User;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.HashMap;import java.util.List;public class UserMapperTest { @Test public void deleteTest () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.deleteUser(4 ); sqlSession.commit(); } } @Test public void selectTest3 () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.getUserByName("阿汤" ); System.out.println("根据name查询用户,查询结果为:" ); System.out.println(user); } } }
注:当传入参数(parameterType
)为int
、String
等简单类型时,传入参数 可以省略不写 。
8.2、实体类类型
示例:在SQL
语句中传入实体类(pojo)类型,实现插入用户的功能,只需修改如下文件:
(1)UserMapper.java
文件:
1 2 3 4 5 6 7 8 9 10 package com.atangbiji.dao;import com.atangbiji.pojo.User;import java.util.List;import java.util.Map;public interface UserMapper { void addUser (User user) ; }
(2)UserMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.UserMapper" > <insert id ="addUser" parameterType ="com.atangbiji.pojo.User" > insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd}) </insert > </mapper >
(3)UserMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.atangbiji.dao;import com.atangbiji.pojo.User;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.HashMap;import java.util.List;public class UserMapperTest { @Test public void insertTest () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.addUser(new User (4 ,"王二" ,"111111" )); sqlSession.commit(); } } }
**注:**在实体类(pojo)已经定义好的情况下,如果传入参数(parameterType
)为实体类类型,那么就不能自定义#{}
占位符中形参的名称。
8.3、万能的Map类型
示例:在SQL
语句中传入Map
类型,实现插入和删除用户的功能,只需修改如下文件:
(1)UserMapper.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 package com.atangbiji.dao;import com.atangbiji.pojo.User;import java.util.List;import java.util.Map;public interface UserMapper { void addUser2 (Map<String,Object> map) ; void deleteUser2 (Map<String,Object> map) ; }
(2)UserMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.UserMapper" > <insert id ="addUser2" parameterType ="map" > insert into mybatis.user (id,name,pwd) values (#{userId},#{userName},#{usePassword}) </insert > <delete id ="deleteUser2" parameterType ="map" > delete from mybatis.user where id = #{userId} </delete > </mapper >
(3)UserMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package com.atangbiji.dao;import com.atangbiji.pojo.User;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.HashMap;import java.util.List;public class UserMapperTest { @Test public void insertTest2 () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ UserMapper mapper = sqlSession.getMapper(UserMapper.class); HashMap<String, Object> hashMap = new HashMap <>(); hashMap.put("userId" ,4 ); hashMap.put("userName" ,"王二" ); hashMap.put("usePassword" ,"111111" ); mapper.addUser2(hashMap); sqlSession.commit(); } } @Test public void deleteTest2 () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); HashMap<String, Object> hashMap = new HashMap <>(); hashMap.put("userId" ,4 ); mapper.deleteUser2(hashMap); sqlSession.commit(); } } }
注:
在实体类(pojo)已经定义好的情况下,如果传入参数(parameterType
)为Map
类型,那么我们可以通过自定义Map
的键名(key
)的方式自定义#{}
占位符中形参的名称。
当映射器接口函数有多个形参时 ,我们往往需要将传入参数设置为Map
类型(或者注解)。
由于传入参数为实体类型时,我们需要在执行SQL
语句的时候new
一个该实体类的对象,并对其中所有的成员变量(字段)进行初始化。实际项目中,当实体类(pojo)的 成员变量(即:数据库表的字段)非常多时 ,我们有时候并不需要将所有的字段都初始化。此时,我们往往可以将传入参数类型设置为Map
类型。
虽然传入参数为简单类型(包括:基本数据类型、String、Date等)和实体类(pojo)类型的标签,完全可以用传入参数为Map
类型的标签替换,但我们往往并不会这样做。
传入参数为Map
类型虽然万能,但不建议随便滥用。
9、ResultMap结果集映射(重点)
使用结果集映射(ResultMap
)的目的 是为了:解决实体类属性名 和数据库表字段名 不一致的问题。
9.1、引例:查询结果为null问题
为了方便理解,我们在之前的演示项目中让实体类(User
)的属性名和数据库(user
)表的字段名保持完全一致。而实际项目可能会出现实体类的属性名 和数据库表的字段名 不一致的现象,如:
mybatis
数据库中user
表的字段分别为:id
、name
、pwd
。
我们在mybatis-02
子项目的基础上,将User
实体类的属性修改为:id
、userName
、password
。
User.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 package com.atangbiji.pojo;public class User { private int id; private String userName; private String password; public User () { } public User (int id, String userName, String password) { this .id = id; this .userName = userName; this .password = password; } public int getId () { return id; } public void setId (int id) { this .id = id; } public String getUserName () { return userName; } public void setUserName (String userName) { this .userName = userName; } public String getPassword () { return password; } public void setPassword (String password) { this .password = password; } @Override public String toString () { return "User{" + "id=" + id + ", userName='" + userName + '\'' + ", password='" + password + '\'' + '}' ; } }
使用MyBatis
实现“根据id查询用户”功能,只需修改如下文件:
(1)UserMapper.java
文件:
1 2 3 4 5 6 7 8 9 package com.atangbiji.dao;import com.atangbiji.pojo.User;public interface UserMapper { User getUserById (int id) ; }
(2)UserMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.UserMapper" > <select id ="getUserById" parameterType ="int" resultType ="User" > select * from mybatis.user where id = #{id} </select > </mapper >
(3)UserMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.atangbiji.dao;import com.atangbiji.pojo.User;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;public class UserMapperTest { @Test public void selectTest () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.getUserById(1 ); System.out.println("根据id查询用户,查询结果为:" ); System.out.println(user); } } }
运行测试函数selectTest
,执行结果如下图所示:
由此我们可以发现:实体类属性名和数据库表字段名一致的可以查到,不一致的查询结果为空(null
)。
原因分析:
select * from user where id = #{id}
等价于 select id,name,pwd from user where id = #{id}
因为此时设置的结果类型(resultType
)为实体类User
,所以mybatis
会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 去对应的实体类中查找相应列名的set
方法设值 , 由于找不到setName()
和setPwd()
方法,所以userName
和password
返回null
。【自动映射】
9.2、解决方案
解决上述问题的关键就是:将SQL
语句查询结果中的name
和pwd
字段映射到实体类的属性。 (即:将数据库表中的name
和pwd
字段映射到实体类的属性。)
方案一:在SQL
语句中为列名指定别名,将别名和java
实体类的属性名一致
UserMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.UserMapper" > <select id ="getUserById" parameterType ="int" resultType ="User" > select id, name as userName, pwd as password from mybatis.user where id = #{id} </select > </mapper >
再次运行测试函数selectTest
,查询成功。执行结果如下图所示:
方案二:使用结果集映射(ResultMap
) 【推荐】
UserMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.UserMapper" > <resultMap id ="UserResultMap" type ="User" > <result column ="name" property ="userName" /> <result column ="pwd" property ="password" /> </resultMap > <select id ="getUserById" parameterType ="int" resultMap ="UserResultMap" > select * from mybatis.user where id = #{id} </select > </mapper >
注:
resultMap
标签中:id
是结果集映射的名称(唯一标识),type
:是映射后的返回结果类型。
result
标签中:column
是数据库表的列名 , property
是对应实体类的属性名。
再次运行测试函数selectTest
,查询成功。执行结果如下图所示:
9.3、自动映射 VS 手动映射
当实体类的属性名和数据库表的字段名完全一致时,MyBatis
可以为我们自动映射查询结果。(此时,我们只需要使用resultType
即可。)
当实体类的属性名和数据库表字段名不一致时,我们需要手动构建一个结果映射(ResultMap
)。
注:
查询结果自动映射(resultType
)和手动映射(resultMap
)可以混用。
当自动映射查询结果时,MyBatis
会获取结果中返回的列名并在 Java
实体类中查找相同名字的属性(忽略大小写)。 这意味着如果发现了ID
列和id
属性,MyBatis
会将列ID
的值赋给 id
属性。
通常数据库列使用大写字母组成的单词命名,单词间用下划线分隔;而 Java
属性一般遵循驼峰命名法 约定。为了在这两种命名方式之间启用自动映射,需要在MyBatis
核心配置文件(mybatis-config.xml
)中将 mapUnderscoreToCamelCase
设置为true
。
10、日志
10.1、日志工厂
如果一个数据库操作出现了异常,我们需要排错,日志就是最好的助手。Mybatis
通过使用内置的日志工厂提供日志功能。我们可以使用日志工厂取代我们之前使用sout
等输出日志。
Mybatis
中具体的日志实现有以下几种工具:
SLF4J
Apache Commons Logging
LOG4J2
LOG4J
(3.5.9 起废弃)
JDK_LOGGING
(Java自带的日志)
具体选择哪个日志实现工具由MyBatis
的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。如果一个都未找到,日志功能就会被禁用。
10.2、使用标准日志
(1)要使用标准日志,我们只需在MyBatis
核心配置文件中将logImpl
设置为:STDOUT_LOGGING
即可。
mybatis-config.xml
文件:
1 2 3 4 5 <settings > <setting name ="logImpl" value ="STDOUT_LOGGING" /> </settings >
(2)运行测试函数,执行结果如下图所示:
此时,我们可以看到控制台有大量的输出,我们可以通过这些输出来判断程序到底哪里出了Bug!
10.3、使用Log4j2
简介:
Log4j
是Apache的一个开源项目,Log4j2
是它的升级版。
官网:https://logging.apache.org/log4j/2.x/
通过使用Log4j
,我们可以控制日志信息输送的目的地:控制台,文本,GUI组件等。
我们也可以控制每一条日志的输出格式。
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
2021年11月24日,Log4j
被曝存在互联网历史上破坏力最惊人的漏洞 。因此,建议使用漏洞被修复过后的最新版本(2.17.1
及以上版本)。
使用步骤:
(1)在父项目的pom.xml
文件中,导入Log4j2
的依赖包。
pom.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > org.apache.logging.log4j</groupId > <artifactId > log4j-api</artifactId > <version > 2.19.0</version > </dependency > <dependency > <groupId > org.apache.logging.log4j</groupId > <artifactId > log4j-core</artifactId > <version > 2.19.0</version > </dependency >
(2)在该子项目的src/main/resources
目录下,新建一个log4j2.xml
配置文件,用于配置LOG4J2
。
log4j2.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?xml version="1.0" encoding="UTF-8" ?> <configuration status ="error" > <appenders > <Console name ="Console" target ="SYSTEM_OUT" > <ThresholdFilter level ="trace" onMatch ="ACCEPT" onMismatch ="DENY" /> <PatternLayout pattern ="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n" /> </Console > <File name ="log" fileName ="D:/logs/log4j2.log" append ="false" > <PatternLayout pattern ="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n" /> </File > <File name ="ERROR" fileName ="D:/logs/error.log" > <ThresholdFilter level ="error" onMatch ="ACCEPT" onMismatch ="DENY" /> <PatternLayout pattern ="%d{yyyy.MM.dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n" /> </File > <RollingFile name ="RollingFile" fileName ="D:/logs/web.log" filePattern ="logs/$${date:yyyy-MM}/web-%d{MM-dd-yyyy}-%i.log.gz" > <PatternLayout pattern ="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n" /> <SizeBasedTriggeringPolicy size ="2MB" /> </RollingFile > </appenders > <loggers > <root level ="trace" > <appender-ref ref ="RollingFile" /> <appender-ref ref ="Console" /> <appender-ref ref ="ERROR" /> <appender-ref ref ="log" /> </root > </loggers > </configuration >
(3)在MyBatis
核心配置文件中将logImpl
设置为:LOG4J2
。
mybatis-config.xml
文件:
1 2 3 4 5 <settings > <setting name ="logImpl" value ="LOG4J2" /> </settings >
(4)在程序中使用LOG4J2
进行输出。
UserMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.atangbiji.dao;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.junit.Test;public class UserMapperTest { private static Logger logger= LogManager.getLogger(LogManager.ROOT_LOGGER_NAME); @Test public void log4j2Test () { logger.trace("log4j2日志输出:This is trace message." ); logger.debug("log4j2日志输出:This is debug message." ); logger.info("log4j2日志输出:This is info message." ); logger.error("log4j2日志输出:This is error message." ); } }
运行测试函数log4j2Test
,控制台输出结果如下图所示:
与此同时,在本地D:/logs/
目录下也会生成LOG4J2
日志文件,如下图所示:
11、分页
思考:为什么需要分页?
在学习mybatis
等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作。如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。
11.1、使用Limit实现分页【推荐】
**【复习】**在MySQL
中我们已经学习了使用Limit实现分页 的语法:
1 select 表达式 from 表名 limit 起始位置,页面大小;
注:
起始位置从0开始计数。
若页面大小为n
,则第m
页的起始位置为:(m-1)×n
。
MyBatis
中使用Limit
实现分页的步骤 :
(1)修改映射器接口文件。
UserMapper.java
文件:
1 2 3 4 5 6 7 8 9 10 package com.atangbiji.dao;import com.atangbiji.pojo.User;import java.util.List;import java.util.Map;public interface UserMapper { List<User> getUserByLimit (Map<String,Object> map) ; }
(2)修改映射器配置文件。
UserMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.UserMapper" > <resultMap id ="UserResultMap" type ="User" > <result column ="name" property ="userName" /> <result column ="pwd" property ="password" /> </resultMap > <select id ="getUserByLimit" parameterType ="map" resultMap ="UserResultMap" > select * from mybatis.user limit #{startIndex},#{pageSize} </select > </mapper >
(3)修改测试文件。
UserMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.atangbiji.dao;import com.atangbiji.pojo.User;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.HashMap;import java.util.List;public class UserMapperTest { @Test public void limitTest () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ UserMapper mapper = sqlSession.getMapper(UserMapper.class); HashMap<String, Object> hashMap = new HashMap <>(); hashMap.put("startIndex" ,1 ); hashMap.put("pageSize" ,2 ); List<User> userList = mapper.getUserByLimit(hashMap); System.out.println("limit分页查询结果为:" ); for (User user : userList) { System.out.println(user); } } } }
运行测试函数limitTest
,控制台输出结果如下图所示:
11.2、PageHelper(了解)
PageHelper
是MyBatis
中一款优秀的分页插件,但当数据量非常大(十万级)时,查询速度缓慢(秒级)。
12、使用注解开发
12.1、面向接口编程
(1)为什么面向接口编程
大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程。
面向接口编程的根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 使得开发变得容易 , 规范性更好。
在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
(2)关于接口的理解
(3)三个面向的区别
面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法。
面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现。
接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题。更多的体现就是对系统整体的架构。
12.2、使用注解配置映射器
12.2.1、注解 VS XML
配置文件
MyBatis
中映射器最初的配置信息是基于XML的,映射的SQL
语句定义在XML配置文件的标签中。而到了MyBatis 3
,它又提供了一种新的映射器配置方法,即:使用注解配置映射器(映射SQL
语句)。
虽然使用注解来映射SQL
语句会使代码显得更加简洁,但由于注解的的表达力和灵活性十分有限,对于稍微复杂一点的SQL
语句,Java 注解不仅力不从心,还会让原本就复杂的SQL
语句变得更加混乱不堪。 因此:
我们只能使用注解映射简单的SQL
语句;
对于复杂的业务,最好还是使用XML
配置文件来映射SQL
语句。
**注:**实际项目开发中,建议尽可能少地在MyBatis
中使用注解。
12.2.2、搭建测试环境
(1)在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目 ,项目名称为mybatis-03
。
(2)填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
(3)在该子项目的src/main/resources
目录下新建MyBatis
的核心配置文件mybatis-config.xml
。
mybatis-config.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration > <properties resource ="db.properties" /> <typeAliases > <typeAlias type ="com.atangbiji.pojo.User" alias ="User" /> </typeAliases > <environments default ="development" > <environment id ="development" > <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 > </configuration >
(4)在该子项目的src/main/resources
目录下,新建一个db.properties
配置文件,用于配置数据库。
db.properties
文件:
1 2 3 4 driver = com.mysql.jdbc.Driver url = jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true username = root password = 123456
(5)同样地,在该子项目的java
目录下新建一个com.atangbiji.utils
包(用于存放我们自己封装的MyBatis
工具类),并在该包下新建一个MyBatisUtils
类,用于获取SqlSession
对象。
MyBatisUtils.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.atangbiji.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;public class MyBatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { String resource = "mybatis-config.xml" ; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder ().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession () { return sqlSessionFactory.openSession(); } public static SqlSession getSqlSession (boolean autoCommit) { return sqlSessionFactory.openSession(autoCommit); } }
(6)同样地,在该子项目的java
目录下新建一个com.atangbiji.pojo
包(用于存放我们自己编写的实体类(pojo
)),并在该包下新建一个User
类,用于实现关系型数据库和业务实体间的映射。
User.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.atangbiji.pojo;public class User { private int id; private String name; private String pwd; public User () { } public User (int id, String name, String pwd) { this .id = id; this .name = name; this .pwd = pwd; } 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 String getPwd () { return pwd; } public void setPwd (String pwd) { this .pwd = pwd; } @Override public String toString () { return "User{" + "id=" + id + ", name='" + name + '\'' + ", pwd='" + pwd + '\'' + '}' ; } }
(7)同样地,在该子项目的java
目录下新建一个com.atangbiji.dao
包(用于存放我们自己编写的持久层代码),并在该包下新建一个UserMapper
映射器接口。
(8)同样地,在该子项目的src/test/java
目录下,新建一个com.atangbiji.dao
包,并在该包下新建一个UserMapperTest
,用于MyBatis
功能测试。
注:
使用注解开发就不再需要mapper.xml
映射文件了。
增、删、改一定记得提交事务。虽然我们可以将事务设置成自动提交,但实际项目中为了数据的安全,建议手动提交事务。
12.2.3、映射插入语句——@Insert ()
(1)在映射器的接口中添加注解
UserMapper.java
文件:
1 2 3 4 5 6 7 8 9 10 package com.atangbiji.dao;import com.atangbiji.pojo.User;import org.apache.ibatis.annotations.Insert;public interface UserMapper { @Insert("insert into mybatis.user(id,name,pwd) values(#{id},#{name},#{pwd})") void addUser (User user) ; }
(2)在MyBatis
的核心配置文件中使用class
注册所有映射器的接口
mybatis-config.xml
文件:
1 2 3 4 <mappers > <mapper class ="com.atangbiji.dao.UserMapper" </mappers >
(3)测试
UserMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.atangbiji.dao;import com.atangbiji.pojo.User;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;public class UserMapperTest { @Test public void insertTest () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession(true )) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); User newUser = new User (4 ,"王二" ,"444444" ); mapper.addUser(newUser); } } }
12.2.4、映射删除语句——@delete ()
(1)在映射器的接口中添加注解
UserMapper.java
文件:
1 2 3 4 5 6 7 8 9 package com.atangbiji.dao;import org.apache.ibatis.annotations.Delete;public interface UserMapper { @Delete("delete from mybatis.user where id=#{id}") void deleteUser (int id) ; }
(2)测试
UserMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.atangbiji.dao;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;public class UserMapperTest { @Test public void deleteTest () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession(true )) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.deleteUser(4 ); } } }
12.2.5、映射更新语句——@update ()
(1)在映射器的接口中添加注解
UserMapper.java
文件:
1 2 3 4 5 6 7 8 9 10 package com.atangbiji.dao;import com.atangbiji.pojo.User;import org.apache.ibatis.annotations.Update;public interface UserMapper { @Update("update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}") void updateUser (User user) ; }
(2)测试
UserMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.atangbiji.dao;import com.atangbiji.pojo.User;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;public class UserMapperTest { @Test public void updateTest () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession(true )) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); User newUser = new User (3 ,"麻子" ,"aaaaaa" ); mapper.updateUser(newUser); } } }
12.2.6、映射查询语句——@select ()
(1)在映射器的接口中添加注解
UserMapper.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.atangbiji.dao;import com.atangbiji.pojo.User;import org.apache.ibatis.annotations.Param;import org.apache.ibatis.annotations.Select;import java.util.List;public interface UserMapper { @Select("select * from mybatis.user") List<User> getUserList () ; @Select("select * from mybatis.user where id = #{id} and name = #{name}") User getUserByIdAndName (@Param("id") int id, @Param("name") String name) ; }
(2)测试
UserMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.atangbiji.dao;import com.atangbiji.pojo.User;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.List;public class UserMapperTest { @Test public void selectTest1 () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.getUserList(); System.out.println("查询结果为:" ); for (User user : userList) { System.out.println(user); } } } @Test public void selectTest2 () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.getUserByIdAndName(1 , "阿汤" ); System.out.println("查询结果为:" ); System.out.println(user); } } }
附:关于@Param
@Param
注解用于给方法中参数起一个名字,其作用等价于传入参数类型为Map
时设置的“键”名 。以下是总结的使用原则:
如果参数是基本数据类型或String
类型, 则可以使用@Param
。
如果参数是 JavaBean(pojo)
, 则不能使用@Param
。
在方法只接受一个参数的情况下,可以不使用@Param
。
在方法接受多个参数的情况下,建议一定要使用@Param
注解给参数命名。
不使用@Param
注解时,参数只能有一个,并且是Javabean
。
12.3、注解的运行原理:反射+动态代理(重点)
注解的本质是:一个继承了Annotation
的特殊接口。其具体实现类是java
运行时,通过 动态代理机制 生成的动态代理类。
13、Lombok
13.1、简介
Lombok
项目是一个java
库,它能够通过注解的方式,在编译时自动为属性生成构造函数、getter/setter
、equals
、hashcode
、toString
方法。这样我们在创建和维护实体类(pojo/javaBean
)时就为我们省去了手动重建这些代码的麻烦,使代码看起来更简洁。
13.2、使用步骤
(1)点击File–>Settings
菜单,在Marketplace
中搜索并安装Lombok
插件。
(2)在项目中导入Lombok
的依赖包。
pom.xml
文件:
1 2 3 4 5 6 <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.24</version > </dependency >
(3)接下来,我们就可以在实体类(pojo
)中使用Lombok
了。如:在User
实体类上添加一个@Data
注解。
1 2 3 4 5 6 7 8 9 10 package com.atangbiji.pojo;import lombok.Data;@Data public class User { private int id; private String name; private String pwd; }
此时,Lombok
就会自动为该实体类生成一个无参构造函数,并为类的所有属性生成setter
/getter
、equals
、canEqual
、hashCode
、toString
方法。如下图所示:
13.2、Lombok常用注解
注解
说明
@Data
注解在类上,会为类生成一个无参构造函数,并为类的所有属性自动生成setter
/getter
、equals
、canEqual
、hashCode
、toString
方法,如为final
属性,则不会为该属性生成setter
方法。
@Getter
/@Setter
注解在属性上,可以为相应的属性自动生成Getter
/Setter
方法
@NonNull
该注解用在属性或构造器上,Lombok
会生成一个非空的声明,可用于校验参数,能帮助避免空指针。
@Cleanup
该注解能帮助我们自动调用close()
方法,例如IO
流。
@EqualsAndHashCode
默认情况下,会使用所有非静态(non-static
)和非瞬态(non-transient
)属性来生成equals
和hasCode
,也能通过exclude
注解来排除一些属性。
@ToString
注解在类上,会生成一个toString()
方法,默认情况下,会输出类名、所有属性(会按照属性定义顺序),用逗号来分割。通过将includeFieldNames
参数设为true
,就能明确的输出toString()
属性。
@NoArgsConstructor
注解在类上,会生成一个无参构造器。
@RequiredArgsConstructor
注解在类上,会生成一个部分参数构造器。
@AllArgsConstructor
注解在类上,会生成一个全参构造器。
14、多对一和一对多查询
14.1、多对一查询
**示例:**从teacher
表和student
表中查询所有学生及对应老师的信息。
14.1.1、引例及测试环境搭建
(1)在数据库中新建一个teacher
表和一个student
表,并向其中插入如下数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 CREATE TABLE `teacher` (`id` INT (10 ) NOT NULL , `name` VARCHAR (30 ) DEFAULT NULL , PRIMARY KEY (`id`)) ENGINE= INNODB DEFAULT CHARSET= utf8 INSERT INTO teacher(`id`, `name`) VALUES (1 , '王老师' );INSERT INTO teacher(`id`, `name`) VALUES (2 , '李老师' );CREATE TABLE `student` (`id` INT (10 ) NOT NULL , `name` VARCHAR (30 ) DEFAULT NULL , `tid` INT (10 ) DEFAULT NULL , PRIMARY KEY (`id`),KEY `fktid` (`tid`), CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)) ENGINE= INNODB DEFAULT CHARSET= utf8 INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1' , '小明' , '1' );INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2' , '小红' , '1' );INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3' , '小张' , '1' );INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4' , '小李' , '1' );INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5' , '小王' , '2' );INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('6' , '小赵' , '2' );
teacher
表:
student
表:
(2)在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目 ,项目名称为mybatis-04
。
(3)填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
(4)在该子项目的src/main/resources
目录下新建MyBatis
的核心配置文件mybatis-config.xml
。
mybatis-config.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration > <properties resource ="db.properties" /> <settings > <setting name ="logImpl" value ="STDOUT_LOGGING" /> </settings > <typeAliases > <package name ="com.atangbiji.pojo" /> </typeAliases > <environments default ="development" > <environment id ="development" > <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 > <package name ="com.atangbiji.dao" /> </mappers > </configuration >
(5)在该子项目的src/main/resources
目录下,新建一个db.properties
配置文件,用于配置数据库。
db.properties
文件:
1 2 3 4 driver = com.mysql.jdbc.Driver url = jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true username = root password = 123456
(6)同样地,在该子项目的java
目录下新建一个com.atangbiji.utils
包(用于存放我们自己封装的MyBatis
工具类),并在该包下新建一个MyBatisUtils
类,用于获取SqlSession
对象。
MyBatisUtils.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.atangbiji.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;public class MyBatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { String resource = "mybatis-config.xml" ; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder ().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession () { return sqlSessionFactory.openSession(); } public static SqlSession getSqlSession (boolean autoCommit) { return sqlSessionFactory.openSession(autoCommit); } }
(7)同样地,在该子项目的java
目录下新建一个com.atangbiji.pojo
包(用于存放我们自己编写的实体类(pojo
)),并在该包下新建一个Teacher
类和一个Student
类,用于实现关系型数据库和业务实体间的映射。
Teacher.java
文件:
1 2 3 4 5 6 7 8 9 10 package com.atangbiji.pojo;import lombok.Data;@Data public class Teacher { private int id; private String name; }
Student.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.atangbiji.pojo;import lombok.Data;@Data public class Student { private int id; private String name; private Teacher teacher; }
注:在多对一查询中,由于多个学生关联一个老师,所以我们应将Teacher
对象(而不是tid
字段)作为Student
类的成员变量。
(8)同样地,在该子项目的java
目录下新建一个com.atangbiji.dao
包(用于存放我们自己编写的持久层代码),并在该包下新建一个TeacherMapper
和一个StudentMapper
映射器接口。
TeacherMapper.java
文件:
1 2 3 4 package com.atangbiji.dao;public interface TeacherMapper {}
StudentMapper.java
文件:
1 2 3 4 5 6 7 8 9 package com.atangbiji.dao;import com.atangbiji.pojo.Student;import java.util.List;public interface StudentMapper { public List<Student> getStudents () ; }
**注:**无论Mapper接口现在有没有需求,都应该先写上,以备后来之需!
(9)同样地,在该子项目的com.atangbiji.dao
包下新建两个映射器配置文件TeacherMapper.xml
和StudentMapper.xml
,并在其中使用标签实现SQL
语句与映射器接口函数的映射。
TeacherMapper.xml
文件:
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.TeacherMapper" > </mapper >
StudentMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.StudentMapper" > <select id ="getStudents" resultType ="Student" > select * from mybatis.student </select > </mapper >
**注:**无论XML映射器配置文件现在有没有需求,都应该先写上,以备后来之需!
(10)同样地,在该子项目的src/test/java
目录下,新建一个com.atangbiji.dao
包,并在该包下新建一个MapperTest
,用于MyBatis
功能测试。
MapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.atangbiji.dao;import com.atangbiji.pojo.Student;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.List;public class MapperTest { @Test public void selectTest1 () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); List<Student> studentList = mapper.getStudents(); System.out.println("查询结果为:" ); for (Student student : studentList) { System.out.println(student); } } } }
运行测试程序selectTest1
,执行结果如下图所示:
由此我们可以发现:实体类属性名和数据库表字段名一致的可以查到,不一致的查询结果为空(null
)。
同样地,我们需要使用结果集映射(ResultMap
)解决上述问题!
14.1.2、方法一:按嵌套查询处理【不推荐】
实现思路:
先查询所有的学生信息;
然后再根据查询出来的学生信息中的老师ID,再去查询该老师的信息。
因此,我们只需在结果集映射中使用association
标签将查询老师信息的SQL
语句的查询结果与对应的实体类属性关联起来即可。
(1)StudentMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.StudentMapper" > <select id ="getStudents" resultMap ="StudentTeacher" > select * from mybatis.student </select > <resultMap id ="StudentTeacher" type ="student" > <association property ="teacher" column ="tid" javaType ="Teacher" select ="getTeacherById" /> </resultMap > <select id ="getTeacherById" resultType ="Teacher" > select * from mybatis.teacher where id = #{id} </select > </mapper >
(2)重新运行测试程序selectTest1
,执行结果如下图所示:
注:
这里使用结果集映射(ResultMap
)的目的 仍然是为了:解决实体类属性名 和数据库表字段名 不一致的问题。
该方法类似于MySQL
中的嵌套查询。
14.1.3、方法二:按联表查询处理【推荐】
实现思路:
先直接联表查询获取所有的学生和老师的信息;
然后再通过结果集映射,在association
标签中,将Student
实体类中的teacher
属性通过Teacher
实体类的属性与teacher
表中的字段关联起来即可。
(1)StudentMapper.java
文件:
1 2 3 4 5 6 7 8 9 package com.atangbiji.dao;import com.atangbiji.pojo.Student;import java.util.List;public interface StudentMapper { public List<Student> getStudents2 () ; }
(2)StudentMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.StudentMapper" > <select id ="getStudents2" resultMap ="StudentTeacher2" > select student.id as sid, student.name as sname, teacher.id as tid, teacher.name as tname from mybatis.student,mybatis.teacher where student.tid = teacher.id </select > <resultMap id ="StudentTeacher2" type ="Student" > <id property ="id" column ="sid" /> <result property ="name" column ="sname" /> <association property ="teacher" javaType ="Teacher" > <result property ="id" column ="tid" /> <result property ="name" column ="tname" /> </association > </resultMap > </mapper >
(3)MapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.atangbiji.dao;import com.atangbiji.pojo.Student;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.List;public class MapperTest { @Test public void selectTest2 () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); List<Student> studentList = mapper.getStudents2(); System.out.println("查询结果为:" ); for (Student student : studentList) { System.out.println(student); } } } }
(4)运行测试程序selectTest2
,执行结果如下图所示:
注:
这里使用结果集映射(ResultMap
)的目的 仍然是为了:解决实体类属性名 和数据库表字段名 不一致的问题。
该方法类似于MySQL
中的联表查询。
14.2、一对多查询
**示例:**从teacher
表和student
表中查询所有老师及所教学生的信息。
14.2.1、引例及测试环境搭建
(1)同样地,在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目 ,项目名称为mybatis-05
。
(2)同样地,填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
(3)同样地,在该子项目的src/main/resources
目录下新建MyBatis
的核心配置文件mybatis-config.xml
。
(4)同样地,在该子项目的src/main/resources
目录下,新建一个db.properties
配置文件,用于配置数据库。
(5)同样地,在该子项目的java
目录下新建一个com.atangbiji.utils
包(用于存放我们自己封装的MyBatis
工具类),并在该包下新建一个MyBatisUtils
类,用于获取SqlSession
对象。
(6)同样地,在该子项目的java
目录下新建一个com.atangbiji.pojo
包(用于存放我们自己编写的实体类(pojo
)),并在该包下新建一个Teacher
类和一个Student
类,用于实现关系型数据库和业务实体间的映射。
Teacher.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.atangbiji.pojo;import lombok.Data;import java.util.List;@Data public class Teacher { private int id; private String name; private List<Student> students; }
Student.java
文件:
1 2 3 4 5 6 7 8 9 10 11 package com.atangbiji.pojo;import lombok.Data;@Data public class Student { private int id; private String name; private int tid; }
注:在一对多查询中,由于一个老师教多个学生(集合),所以我们应将List<Student>
对象(而不是Student
对象)作为Teacher
类的成员变量。
(7)同样地,在该子项目的java
目录下新建一个com.atangbiji.dao
包(用于存放我们自己编写的持久层代码),并在该包下新建一个TeacherMapper
和一个StudentMapper
映射器接口。
TeacherMapper.java
文件:
1 2 3 4 5 6 7 8 9 package com.atangbiji.dao;import com.atangbiji.pojo.Teacher;import java.util.List;public interface TeacherMapper { public List<Teacher> getTeachers () ; }
StudentMapper.java
文件:
1 2 3 4 package com.atangbiji.dao;public interface StudentMapper {}
**注:**无论Mapper接口现在有没有需求,都应该先写上,以备后来之需!
(8)同样地,在该子项目的com.atangbiji.dao
包下新建两个映射器配置文件TeacherMapper.xml
和StudentMapper.xml
,并在其中使用标签实现SQL
语句与映射器接口函数的映射。
TeacherMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.TeacherMapper" > <select id ="getTeachers" resultType ="Teacher" > select * from mybatis.teacher </select > </mapper >
StudentMapper.xml
文件:
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.StudentMapper" > </mapper >
**注:**无论XML映射器配置文件现在有没有需求,都应该先写上,以备后来之需!
(9)同样地,在该子项目的src/test/java
目录下,新建一个com.atangbiji.dao
包,并在该包下新建一个MapperTest
,用于MyBatis
功能测试。
MapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.atangbiji.dao;import com.atangbiji.pojo.Teacher;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.List;public class MapperTest { @Test public void selectTest3 () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); List<Teacher> teacherList = mapper.getTeachers(); System.out.println("查询结果为:" ); for (Teacher teacher : teacherList) { System.out.println(teacher); } } } }
运行测试程序selectTest3
,执行结果如下图所示:
由此我们可以发现:实体类属性名和数据库表字段名一致的可以查到,不一致的查询结果为空(null
)。
同样地,我们需要使用结果集映射(ResultMap
)解决上述问题!
14.2.2、方法一:按嵌套查询处理【不推荐】
实现思路:
先查询所有的老师信息;
然后再根据查询出来的老师ID,再去查询该老师所教学生的信息。
因此,我们只需在结果集映射中使用collection
标签将查询老师所教学生信息的SQL
语句的查询结果与对应的实体类属性关联起来即可。
(1)TeacherMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.TeacherMapper" > <select id ="getTeachers" resultMap ="TeacherStudent" > select * from mybatis.teacher </select > <resultMap id ="TeacherStudent" type ="Teacher" > <collection property ="students" column ="id" javaType ="ArrayList" ofType ="Student" select ="getStudentByTeacherId" /> </resultMap > <select id ="getStudentByTeacherId" resultType ="Student" > select * from mybatis.student where tid = #{id} </select > </mapper >
(2)重新运行测试程序selectTest3
,执行结果如下图所示:
注:
这里使用结果集映射(ResultMap
)的目的 仍然是为了:解决实体类属性名 和数据库表字段名 不一致的问题。
该方法类似于MySQL
中的嵌套查询。
14.2.3、方法二:按联表查询处理【推荐】
实现思路:
先直接联表查询获取所有老师及所教学生的信息;
然后再通过结果集映射,在collection
标签中,将Teacher
实体类中的students
属性通过Student
实体类的属性与student
表中的字段关联起来即可。
(1)TeacherMapper.java
文件:
1 2 3 4 5 6 7 8 9 package com.atangbiji.dao;import com.atangbiji.pojo.Teacher;import java.util.List;public interface TeacherMapper { public List<Teacher> getTeachers2 () ; }
(2)TeacherMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.TeacherMapper" > <select id ="getTeachers2" resultMap ="TeacherStudent2" > select teacher.id as tid, teacher.name as tname, student.id as sid, student.name as sname from mybatis.teacher,mybatis.student where teacher.id = student.tid </select > <resultMap id ="TeacherStudent2" type ="teacher" > <id property ="id" column ="tid" /> <result property ="name" column ="tname" /> <collection property ="students" ofType ="Student" > <id property ="id" column ="sid" /> <result property ="name" column ="sname" /> <result property ="tid" column ="tid" /> </collection > </resultMap > </mapper >
(3)MapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.atangbiji.dao;import com.atangbiji.pojo.Teacher;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.List;public class MapperTest { @Test public void selectTest4 () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); List<Teacher> teacherList = mapper.getTeachers2(); System.out.println("查询结果为:" ); for (Teacher teacher : teacherList) { System.out.println(teacher); } } } }
(4)运行测试程序selectTest4
,执行结果如下图所示:
注:
这里使用结果集映射(ResultMap
)的目的 仍然是为了:解决实体类属性名 和数据库表字段名 不一致的问题。
该方法类似于MySQL
中的联表查询。
14.3、小结
**注:**实际项目开发中,我们应当:
保证SQL
的可读性,尽量通俗易懂。
根据实际要求,尽量编写性能更高的SQL
语句。
注意属性名和字段不一致的问题。
注意一对多和多对一 中:字段和属性对应的问题。
尽量使用Log4j2
,通过日志来查看自己的错误。
15、动态SQL
15.1、什么是动态SQL
动态SQL
指的是:根据不同的查询条件 , 生成不同的SQL
语句。
我们之前写的SQL
语句都比较简单,如果有比较复杂的业务,我们需要写复杂的SQL
语句,往往需要拼接,而拼接SQL
。稍微不注意,由于引号,空格等缺失可能都会导致错误。
那么怎么去解决这个问题呢?这就要使用MyBatis
动态SQL
,通过if, choose, when, otherwise, trim, where, set, foreach
等标签,可组合成非常灵活的SQL
语句,从而在提高SQL
语句的准确性的同时,也大大提高了开发人员的效率。
15.2、动态SQL环境搭建
(1)在数据库中新建一个blog
(博客)表。
1 2 3 4 5 6 7 CREATE TABLE `blog` (`id` varchar (50 ) NOT NULL COMMENT '博客id' , `title` varchar (100 ) NOT NULL COMMENT '博客标题' , `author` varchar (30 ) NOT NULL COMMENT '博客作者' , `create_time` datetime NOT NULL COMMENT '创建时间' , `views` int (30 ) NOT NULL COMMENT '浏览量' ) ENGINE= InnoDB DEFAULT CHARSET= utf8
(2)同样地,在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目 ,项目名称为mybatis-06
。
(3)同样地,填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
(4)同样地,在该子项目的src/main/resources
目录下新建MyBatis
的核心配置文件mybatis-config.xml
。
注:本项目需要在“设置”中开启 驼峰命名 自动映射。
mybatis-config.xml
文件:
1 2 3 4 5 6 7 <settings > <setting name ="mapUnderscoreToCamelCase" value ="true" /> <setting name ="logImpl" value ="STDOUT_LOGGING" /> </settings >
(5)同样地,在该子项目的src/main/resources
目录下,新建一个db.properties
配置文件,用于配置数据库。
(6)同样地,在该子项目的java
目录下新建一个com.atangbiji.utils
包(用于存放我们自己封装的MyBatis
工具类),并在该包下新建一个MyBatisUtils
类,用于获取SqlSession
对象。
(7)在该子项目的com.atangbiji.utils
包下新建一个IDUtil
工具类,用于生成UUID
。
IDUtil.java
文件:
1 2 3 4 5 6 7 8 9 10 11 package com.atangbiji.utils;import java.util.UUID;public class IDUtil { public static String generateId () { return UUID.randomUUID().toString().replace("-" ,"" ); } }
注 :UUID
(Universally Unique Identifier
):通用唯一识别码 。
(8)同样地,在该子项目的java
目录下新建一个com.atangbiji.pojo
包(用于存放我们自己编写的实体类(pojo
)),并在该包下新建一个Blog
类,用于实现关系型数据库和业务实体间的映射。
Blog.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.atangbiji.pojo;import lombok.Data;import java.util.Date;@Data public class Blog { private String id; private String title; private String author; private Date createTime; private int views; }
(9)同样地,在该子项目的java
目录下新建一个com.atangbiji.dao
包(用于存放我们自己编写的持久层代码),并在该包下新建一个BlogMapper
映射器接口。
BlogMapper.java
文件:
1 2 3 4 5 6 7 8 package com.atangbiji.dao;import com.atangbiji.pojo.Blog;public interface BlogMapper { int addBlog (Blog blog) ; }
(10)同样地,在该子项目的com.atangbiji.dao
包下新建一个映射器配置文件BlogMapper.xml
,并在其中使用标签实现SQL
语句与映射器接口函数的映射。
BlogMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.BlogMapper" > <insert id ="addBlog" parameterType ="blog" > insert into mybatis.blog (id, title, author, create_time, views) values (#{id},#{title},#{author},#{createTime},#{views}); </insert > </mapper >
(11)同样地,在该子项目的src/test/java
目录下,新建一个com.atangbiji.dao
包,并在该包下新建一个MapperTest
,用于MyBatis
功能测试。
MapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package com.atangbiji.dao;import com.atangbiji.pojo.Blog;import com.atangbiji.utils.IDUtil;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.Date;public class MapperTest { @Test public void addInitBlog () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()){ BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); Blog blog = new Blog (); blog.setId(IDUtil.generateId()); blog.setTitle("Mybatis如此简单" ); blog.setAuthor("阿汤笔迹" ); blog.setCreateTime(new Date ()); blog.setViews(9999 ); mapper.addBlog(blog); blog.setId(IDUtil.generateId()); blog.setTitle("Java如此简单" ); mapper.addBlog(blog); blog.setId(IDUtil.generateId()); blog.setTitle("Spring如此简单" ); mapper.addBlog(blog); blog.setId(IDUtil.generateId()); blog.setTitle("微服务如此简单" ); mapper.addBlog(blog); sqlSession.commit(); } } }
运行测试程序addInitBlog
,完成数据初始化,执行结果如下图所示:
15.3、if标签
**示例:**根据作者和博客标题来查询博客。若传入标题非空,则根据标题查询;若传入作者非空,则根据作者来查询;若两者均非空,则根据标题和作者查询;否则,查询所有博客。
(1)BlogMapper.java
文件:
1 2 3 4 5 6 7 8 9 10 package com.atangbiji.dao;import com.atangbiji.pojo.Blog;import java.util.List;import java.util.Map;public interface BlogMapper { List<Blog> queryBlogByAuthorAndBlogName (Map map) ; }
(2)BlogMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.BlogMapper" > <select id ="queryBlogByAuthorAndBlogName" parameterType ="map" resultType ="blog" > select * from mybatis.blog where 1 = 1 <if test ="title != null" > and title = #{title} </if > <if test ="author != null" > and author = #{author} </if > </select > </mapper >
(3)MapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.atangbiji.dao;import com.atangbiji.pojo.Blog;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.HashMap;import java.util.List;public class MapperTest { @Test public void TestIf () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, String> map = new HashMap <>(); map.put("title" ,"Mybatis如此简单" ); map.put("author" ,"阿汤笔迹" ); List<Blog> blogs = mapper.queryBlogByAuthorAndBlogName(map); System.out.println("查询结果为:" ); for (Blog blog : blogs) { System.out.println(blog); } } } }
运行测试程序TestIf
,执行结果如下图所示:
15.4、where标签
上述if
语句中如果在where
子句中不加1 = 1 and
,即:
1 2 3 4 5 6 7 8 9 10 <select id ="queryBlogByAuthorAndBlogName" parameterType ="map" resultType ="blog" > select * from mybatis.blog where <if test ="title != null" > title = #{title} </if > <if test ="author != null" > and author = #{author} </if > </select >
这样写我们可以看到:
当title
非空,author
为空(null
)时,查询语句为select * from mybatis.blog where title = #{title}
,此时SQL
可以正常执行。
但是当title
为空(null
),author
非空时,查询语句为select * from mybatis.blog where and author = #{author}
,这是错误的SQL
语句,此时SQL
执行会报错。
为了解决上述问题,MyBatis
提供了一个简单且适合大多数场景的where
标签。我们只需将上述SQL
语句做如下修改即可:
1 2 3 4 5 6 7 8 9 10 11 12 <select id ="queryBlogByAuthorAndBlogName" parameterType ="map" resultType ="blog" > select * from mybatis.blog <where > <if test ="title != null" > title = #{title} </if > <if test ="author != null" > and author = #{author} </if > </where > </select >
注:
where
标签只会在子标签返回任何内容的情况下才插入 “WHERE” 子句。
若子句的开头为 “AND” 或 “OR”,where
标签也会自动将它们去除。
附:trim标签实现where标签功能(了解)
我们也可以通过自定义trim
标签来定制where
元素的功能。比如,和where
标签等价的自定义trim
标签为:
1 2 3 <trim prefix ="WHERE" prefixOverrides ="AND |OR " > ... </trim >
其中:prefixOverrides
属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有prefixOverrides
属性中指定的内容,并且插入prefix
属性中指定的内容。
15.5、set标签
上面的查询SQL
语句中包含where
关键字。同理,如果我们在进行**更新(update
)**操作的时候,含有set
关键词,我们同样可以使用set
标签动态包含需要更新的列,忽略其它不更新的列。如:
(1)BlogMapper.java
文件:
1 2 3 4 5 6 7 8 package com.atangbiji.dao;import java.util.Map;public interface BlogMapper { int updateBlog (Map map) ; }
(2)BlogMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.BlogMapper" > <update id ="updateBlog" parameterType ="map" > update mybatis.blog <set > <if test ="title != null" > title = #{title}, </if > <if test ="author != null" > author = #{author} </if > </set > where id = #{id} </update > </mapper >
(3)MapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.atangbiji.dao;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.HashMap;public class MapperTest { @Test public void TestSet () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, String> map = new HashMap <>(); map.put("id" , "db28209746564b778b6c59415ef911d5" ); map.put("title" ,"动态SQL如此简单" ); map.put("author" ,"阿汤笔迹" ); mapper.updateBlog(map); sqlSession.commit(); } } }
运行测试程序TestSet
,完成数据更新操作,执行结果如下图所示:
注: set
标签会动态地在行首插入SET
关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
附:trim
标签实现set标签功能(了解)
我们也可以通过使用trim
标签来达到set
标签同样的效果:
1 2 3 <trim prefix ="SET" suffixOverrides ="," > ... </trim >
**注:**我们覆盖了后缀值设置,并且自定义了前缀值。
15.6、choose(when、otherwise)标签
有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用choose
标签可以解决此类问题。类似于Java
的switch
语句。
**示例:**根据作者和博客标题来查询博客。依次判断标题和作战是否非空,只要任何一个判据非空,则直接根据该判据查询。否则,根据浏览量查询。
(1)BlogMapper.java
文件:
1 2 3 4 5 6 7 8 9 10 package com.atangbiji.dao;import com.atangbiji.pojo.Blog;import java.util.List;import java.util.Map;public interface BlogMapper { List<Blog> queryBlogByChoose (Map map) ; }
(2)BlogMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.BlogMapper" > <select id ="queryBlogByChoose" parameterType ="map" resultType ="blog" > select * from mybatis.blog <where > <choose > <when test ="title != null" > title = #{title} </when > <when test ="author != null" > and author = #{author} </when > <otherwise > and views = #{views} </otherwise > </choose > </where > </select > </mapper >
(3)MapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package com.atangbiji.dao;import com.atangbiji.pojo.Blog;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.HashMap;import java.util.List;public class MapperTest { @Test public void TestChoose () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, Object> map = new HashMap <>(); map.put("title" ,"动态SQL如此简单" ); map.put("author" ,"阿汤笔迹" ); map.put("views" ,9999 ); List<Blog> blogs = mapper.queryBlogByChoose(map); System.out.println("查询结果为:" ); for (Blog blog : blogs) { System.out.println(blog); } } } }
运行测试程序TestChoose
,执行结果如下图所示:
15.7、SQL片段
有时候某个SQL
语句我们可能用的特别多,为了增加代码的重用性,简化代码,我们可以将这些代码抽取出来,然后使用时直接调用。
(1)提取SQL
片段:
BlogMapper.xml
文件:
1 2 3 4 5 6 7 8 9 <sql id ="if-title-author" > <if test ="title != null" > title = #{title} </if > <if test ="author != null" > and author = #{author} </if > </sql >
(2)引用SQL
片段:
BlogMapper.xml
文件:
1 2 3 4 5 6 7 8 9 <select id ="queryBlogByAuthorAndBlogName" parameterType ="map" resultType ="blog" > select * from mybatis.blog <where > <include refid ="if-title-author" > </include > </where > </select >
注:
①为了提高片段的可重用性,建议最好基于单表来定义SQL
片段。
②在SQL
片段中不要包括<where>……</where>
标签。
15.8、foreach标签
foreach
标签的常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。
1 SELECT 字段名1 [,字段名2 ,……] FROM 表名 WHERE 字段名i IN (值1 ,值2 ,……);
示例 :查询blog
表中title
分别为Mybatis如此简单
、Spring如此简单
、动态SQL如此简单
的博客信息。
(1)编写接口方法。
BlogMapper.java
文件:
1 2 3 4 5 6 7 8 9 10 package com.atangbiji.dao;import com.atangbiji.pojo.Blog;import java.util.List;import java.util.Map;public interface BlogMapper { List<Blog> queryBlogByForeach (Map map) ; }
(2)编写SQL
语句。
BlogMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.BlogMapper" > <select id ="queryBlogByForeach" parameterType ="map" resultType ="blog" > select * from mybatis.blog <where > <foreach collection ="titles" item ="title" index ="index" open ="title in (" separator ="," close =")" > #{title} </foreach > </where > </select > </mapper >
(3)测试。
MapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.atangbiji.dao;import com.atangbiji.pojo.Blog;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.*;public class MapperTest { @Test public void TestForeach () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap<String, ArrayList> map = new HashMap <>(); ArrayList<String> titles = new ArrayList <>(); titles.add("Mybatis如此简单" ); titles.add("Spring如此简单" ); titles.add("动态SQL如此简单" ); map.put("titles" ,titles); List<Blog> blogs = mapper.queryBlogByForeach(map); System.out.println("查询结果为:" ); for (Blog blog : blogs) { System.out.println(blog); } } } }
运行测试程序TestForeach
,执行结果如下图所示:
**注:**我们可以将任何可迭代对象(如List
、Set
等)、Map
对象或者数组对象作为集合参数传递给foreach
。当使用可迭代对象或者数组时,index
是当前迭代的序号,item
的值是本次迭代获取到的元素。当使用Map
对象(或者Map.Entry
对象的集合)时,index
是键,item
是值。
15.9、小结
其实动态SQL
语句的编写往往就是一个SQL
语句(字符串)拼接的问题。
为了保证拼接准确,我们最好首先要写出完整的SQL
语句出来,然后再通过MyBatis
修改对应的动态SQL
,防止出错。
多在实践中使用才是熟练掌握它的技巧。
16、缓存
16.1、简介
16.1.1、什么是缓存(Cache)
缓存(Cache)就是:存储在内存中的临时数据。
将用户经常查询的数据放在缓存(内存 )中,当用户再次查询相同数据时就不用从磁盘上(关系型数据库数据文件)查询了,而是从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
16.1.2、为什么使用缓存
减少和数据库的交互次数,减少系统开销,提高系统效率。
16.1.3、什么样的数据能使用缓存
16.2、Mybatis缓存
16.3、一级缓存
一级缓存也叫**“本地缓存”**:
一级缓存的生命周期与一个SqlSession
对象的生命周期相同,当一个SqlSession
对象关闭时,与之对应的一级缓存同步清除。
与数据库同一次会话(SqlSession
)期间查询到的数据会放在本地缓存中。
以后如果需要获取相同的数据,直接从缓存中获取,没必须再去查询数据库。
16.3.1、测试
(1)编写接口方法。
BlogMapper.java
文件:
1 2 3 4 5 6 7 8 package com.atangbiji.dao;import com.atangbiji.pojo.Blog;public interface BlogMapper { Blog queryBlogById (String id) ; }
(2)编写SQL
语句。
BlogMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.BlogMapper" > <select id ="queryBlogById" parameterType ="string" resultType ="blog" > select * from mybatis.blog where id = #{id} </select > </mapper >
(3)测试。
BlogMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.atangbiji.dao;import com.atangbiji.pojo.Blog;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;public class MapperTest { @Test public void TestQueryBlogById () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); Blog blog1 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33" ); System.out.println("第一次查询结果为:" + blog1); Blog blog2 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33" ); System.out.println("第二次查询结果为:" + blog2); System.out.println(blog1 == blog2); } } }
运行测试程序TestQueryBlogById
,执行结果如下图所示:
16.3.2、一级缓存失效的四种情况
(1)sqlSession
不同
BlogMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.atangbiji.dao;import com.atangbiji.pojo.Blog;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;public class MapperTest { @Test public void TestQueryBlogById2 () { Blog blog1 = new Blog (); Blog blog2 = new Blog (); try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); blog1 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33" ); System.out.println("第一次查询结果为:" + blog1); } try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); blog2 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33" ); System.out.println("第二次查询结果为:" + blog2); } System.out.println(blog1 == blog2); } }
运行测试程序TestQueryBlogById2
,执行结果如下图所示:
观察结果:发现发送了两条SQL
语句!
结论:每个sqlSession
中的缓存相互独立。
(2)sqlSession
相同,查询条件不同
BlogMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.atangbiji.dao;import com.atangbiji.pojo.Blog;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;public class MapperTest { @Test public void TestQueryBlogById3 () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); Blog blog1 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33" ); System.out.println("第一次查询结果为:" + blog1); Blog blog2 = mapper.queryBlogById("7aa61e8d1e2f40db89d47b1b18ba629a" ); System.out.println("第二次查询结果为:" + blog2); System.out.println(blog1 == blog2); } } }
运行测试程序TestQueryBlogById3
,执行结果如下图所示:
观察结果:发现发送了两条SQL
语句!
结论:当前缓存中,不存在这个数据
(3)sqlSession
相同,两次查询之间执行了增删改操作
BlogMapper.java
文件:
1 2 3 4 5 6 7 8 9 10 package com.atangbiji.dao;import com.atangbiji.pojo.Blog;public interface BlogMapper { int addBlog (Blog blog) ; Blog queryBlogById (String id) ; }
BlogMapper.xml
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atangbiji.dao.BlogMapper" > <insert id ="addBlog" parameterType ="blog" > insert into mybatis.blog (id, title, author, create_time, views) values (#{id},#{title},#{author},#{createTime},#{views}); </insert > <select id ="queryBlogById" parameterType ="string" resultType ="blog" > select * from mybatis.blog where id = #{id} </select > </mapper >
BlogMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package com.atangbiji.dao;import com.atangbiji.pojo.Blog;import com.atangbiji.utils.IDUtil;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.*;public class MapperTest { @Test public void TestQueryBlogById4 () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); Blog blog1 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33" ); System.out.println("第一次查询结果为:" + blog1); Blog blog = new Blog (); blog.setId(IDUtil.generateId()); blog.setTitle("SpringMVC如此简单" ); blog.setAuthor("阿汤笔迹" ); blog.setCreateTime(new Date ()); blog.setViews(8888 ); mapper.addBlog(blog); sqlSession.commit(); Blog blog2 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33" ); System.out.println("第二次查询结果为:" + blog2); System.out.println(blog1 == blog2); } } }
运行测试程序TestQueryBlogById4
,执行结果如下图所示:
观察结果:两次查询的中间执行了增删改操作后,重新执行了。
结论:因为xml
映射配置文件中的所有insert
、update
和delete
语句会刷新缓存 。
(4)sqlSession
相同,两次查询中间手动清除一级缓存
BlogMapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.atangbiji.dao;import com.atangbiji.pojo.Blog;import com.atangbiji.utils.IDUtil;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;import java.util.*;public class MapperTest { @Test public void TestQueryBlogById5 () { try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); Blog blog1 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33" ); System.out.println("第一次查询结果为:" + blog1); sqlSession.clearCache(); Blog blog2 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33" ); System.out.println("第二次查询结果为:" + blog2); System.out.println(blog1 == blog2); } } }
运行测试程序TestQueryBlogById5
,执行结果如下图所示:
注:一级缓存就是一个map
。
16.4、二级缓存
二级缓存也叫**“全局缓存”**,一级缓存作用域太低了,所以诞生了二级缓存。
基于命名空间(namespace
)级别的缓存。
一个命名空间(XML映射器配置文件),对应一个二级缓存。
**注:**二级缓存只作用于cache
标签所在的XML映射文件中的语句。
工作机制:
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
如果当前会话关闭了,该会话对应的一级缓存就没了;此时,一级缓存中的数据被转到 二级缓存中;
新的会话查询信息,就可以直接从二级缓存中获取内容;
不同的mapper
查出的数据会放在自己对应的缓存(map
)中。
16.4.1、使用步骤
(1)在MyBatis
核心配置文件中将全局缓存设置为开启状态。
mybatis-config.xml
文件:
1 2 3 4 <settings > <setting name ="cacheEnabled" value ="true" /> </settings >
(2)要启用全局的二级缓存,只需要在对应的SQL
映射文件中添加一行代码即可:
xxxMapper.xml
文件:
此时达到的效果如下:
映射语句文件中的所有select
语句的结果将会被缓存。
映射语句文件中的所有insert
、update
和delete
语句会刷新缓存。
缓存会使用最近最少使用算法(LRU, Least Recently Used
)来清除不需要的缓存。
缓存不会定时进行刷新(即:没有刷新间隔)。
缓存会保存列表或对象(无论查询方法返回哪种)的1024个引用。
缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
当然,我们也可以在每个xxxMapper.xml
中配置当前XML映射器配置文件中的二级缓存策略。
1 2 <cache eviction ="LRU" flushInterval ="60000" size ="512" readOnly ="true" />
注:
eviction
:清除策略,默认为LRU
。可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。
FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。
SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。
WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
flushInterval
:刷新间隔,单位(ms)
size
:存储对象或列表的引用数目(想缓存对象的大小和运行环境中可用的内存资源,默认值是 1024。)
readOnly
:只读。
只读的缓存会给所有调用者返回缓存对象的相同实例,这些对象不能被修改,性能提升明显。
而可读写的缓存会(通过序列化)返回缓存对象的拷贝,速度上会慢一些,但是更安全。
默认值为false
。
(3)测试。
MapperTest.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.atangbiji.dao;import com.atangbiji.pojo.Blog;import com.atangbiji.utils.MyBatisUtils;import org.apache.ibatis.session.SqlSession;import org.junit.Test;public class MapperTest { @Test public void TestQueryBlogById2 () { Blog blog1 = new Blog (); Blog blog2 = new Blog (); try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); blog1 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33" ); System.out.println("第一次查询结果为:" + blog1); } try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) { BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); blog2 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33" ); System.out.println("第二次查询结果为:" + blog2); } System.out.println(blog1 == blog2); } }
再次运行测试程序TestQueryBlogById2
,会发现此时执行结果变为:
16.4.2、结论
只要开启了二级缓存,我们在同一个Mapper中的查询 ,可以在二级缓存中拿到数据。
查出的数据都会被默认先放在一级缓存中。
只有会话提交或者关闭 时,一级缓存中的数据才会转到 二级缓存中。
16.5、Mybatis缓存原理
(本讲完,系列博文持续更新中…… )
关注**“阿汤笔迹”** 微信公众号,获取更多学习笔记。
原文地址:http://www.atangbiji.com/2022/11/18/MybatisInDetail
博主最新文章在个人博客 http://www.atangbiji.com/ 发布。