Mybatis 入门及实践
文章目录
- Mybatis 入门及实践
- 前言
- 一. 简介
- 二. 入手案例
- 2.1 前置准备
- 2.1.1 Maven依赖
- 2.1.2 sql准备
- 2.1.3 Log4j2 配置
- 1. Maven引入
- 2. log4j2.xml
- 2.2 代码流程构建步骤
- 2.2.1 Mybatis前置知识
- 2.2.2 步骤流程
- 2.2.3 代码实现
- 三. XML映射器
- ==前置代码==
- 3.1 select
- 3.1.1 元素属性
- 3.1.2 无参查询
- 1. 查询全部记录,通过List形式回参
- 2. 查询全部记录,通过Map形式回参
- 3.1.3 传参查询
- 1.单个参数类型传参
- 2.多个基础数据类型map集合传参
- 3.引用类型传参
- 3.2 insert, update&delete
- ==前置==
- 3.2.1 元素属性
- 3.2.2 insert
- 3.2.3 update
- 3.2.4 delete
- 3.3 ResultMap - 关联查询
- ResultMap里的标签
- 1. constructor
- 2. association(一对一)
- 3.collection(一对多)
- 4. discriminator
- 5. 多对多查询
- 3.4 存储过程调用
- 四. Mybatis 代理模式开发
- 4.1 简介
- 4.2 代理接口编写
- 4.2.1 步骤流程
- 4.2.2 代码实现
- 4.2 crud代码实现
- 4.3 深入查询
- 4.3.1 传参
- 1. 通过单个基本数据类型查询
- 2. 通过多个基本数据类型查询
- 3. 通过实体类查询
- 4. 通过多个实体类查询
- 5. 通过map集合查询
- 4.3.2 模糊查询
- 4.4 动态sql
- 4.4.1 if
- 4.4.2 choose, when&otherwise
- 4.4.3 trim, where&set
- 4.4.4 foreach
- 4.4.5 sql&bind
- 4.5 注解 - 增删改查
- 4.5.1 步骤流程
- 4.5.2 代码实现
- 五. 遇见的异常
- 5.1 异常一:Type interface com.zhanghp.dao.mapper.DemoMapper is already known to the MapperRegistry.
- 5.2 异常二:Cannot define both nestedQueryId and nestedResultMapId in property dept
- 5.3 异常三:Mapped Statements collection does not contain value for queryMultiToMulti
前言
⭐️ 代码地址:https://gitee.com/zhp1221/java/tree/master/lab_04_mybatis
⭐️ 这里sql准备了2个文件,为什么分2个库来练习?
因为笔者来练习实现不同数据库之间的使用。原理是切换environment
配置。所以大家请自行创建2个数据库,sql文件名为数据库的名称,分别是deep_practice
,test
ConnectOptionalEnvironmentUtil :通过这个工具类的编写,传入environment
指定的id(environment
在mybatis-config.xml文件里配置),就可实现另一个数据库的使用
ResultMapSqlDemo :对ConnectOptionalEnvironmentUtil
的使用
⭐️ 如果本文章对你们有帮助,记得给笔者点个赞,编写不易!
一. 简介
Mybatis - 官方文档
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
二. 入手案例
2.1 前置准备
2.1.1 Maven依赖
<dependencies>
<!-- mysql数据库链接 -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!-- mybatis orm层依赖 -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<!-- junit 测试依赖 -->
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- 简化java臃肿代码依赖包 -->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<!--hutool 工具集合引入-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.7</version>
<scope>compile</scope>
</dependency>
</dependencies>
2.1.2 sql准备
-- 建表
CREATE TABLE `demo` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(10) DEFAULT NULL COMMENT '姓名',
`age` int DEFAULT NULL COMMENT '年龄',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='mybatis - demo表';
-- 测试数据
INSERT INTO `demo` (`id`, `name`, `age`) VALUES (1, '张三', 18);
INSERT INTO `demo` (`id`, `name`, `age`) VALUES (2, '李四', 20);
2.1.3 Log4j2 配置
用于打印sql,方便查看自己所构建的sql语句运行过程
1. Maven引入
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.1</version>
</dependency>
2. log4j2.xml
在resources包下创建
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<loggers>
<Logger name="com.zhanghp" level="debug">
</Logger>
</loggers>
</Configuration>
2.2 代码流程构建步骤
2.2.1 Mybatis前置知识
Mybatis的核心类是SqlSessionFactory,我们需要通过此类来建立数据库连接,并实现增删改查的代码编写。
而SqlSessionFactory实例需通过SqlSessionFactoryBuilder获得。SqlSessionFactoryBuilder则通过XML/Configuration的实例来构建。这里笔者采用的是XML构建SqlSessionFactoryBuilder。
另一种Configuration实例化,读者可通过访问官网链接,进行详细的了解。
2.2.2 步骤流程
- SqlSessionFactory实例代码编写:数据库与java代码相连的桥梁
- mybatis-config.xml配置:进行SqlSessionFacotryBuilder实例创建
- 数据库对应的实体类Demo编写:接收及传递对象
- DemoMapper.xml编写:进行数据库的增删改查
- 通过Junit来测试编写的代码
2.2.3 代码实现
目录结构
-
SqlSessionFactory实例代码编写
/** * XML 中构建 SqlSessionFactory * * @author zhanghp * @date 2023/6/25 14:24 */ public class ConnectionDBWithXml { /** * xml配置路径 */ private static final String RESOURCE = "mybatis-config.xml"; /** * 连接数据库的工厂类 */ private static final SqlSessionFactory SQL_SESSION_FACTORY; static { InputStream resourceAsStream = null; try { // MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。 resourceAsStream = Resources.getResourceAsStream(RESOURCE); } catch (IOException e) { e.printStackTrace(); } // 初始化SqlSessionFactory SQL_SESSION_FACTORY = new SqlSessionFactoryBuilder().build(resourceAsStream); } public void connect() { // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法 try (SqlSession session = SQL_SESSION_FACTORY.openSession()) { // DemoMapper.xml中的select的id:selectOneRecord Demo record = session.selectOne("selectOneRecord"); System.out.println(record); System.out.println("---------------------------------"); List<Demo> recordList = session.selectList("selectDemo"); if (ObjectUtil.isNotEmpty(record)) { recordList.forEach(System.out::println); } } } }
-
mybatis-config.xml 配置
我这里创建了一个jdbc.properties文件,目的是为了方便统一管理数据库。
在mybatis-config.xml文件中,可通过properties属性,来引用该文件,并在环境配置中,通过**${}**来引用jdbc.properties里的属性。
jdbc.properties
jdbc_driver=com.mysql.cj.jdbc.Driver jdbc_url=jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowPublicKeyRetrieval=true jdbc_username=root jdbc_password=zhp.1221
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> <!-- 引入外部文件配置 --> <properties resource="jdbc.properties"/> <!-- 类型别名 --> <typeAliases> <package name="com.zhanghp.dao.pojo"/> </typeAliases> <!-- 环境配置 --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="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> <mappers> <!-- 扫描xml路径 --> <mapper resource="mappers/DemoMapper.xml"/> </mappers> </configuration>
-
实体类Demo
/** * demo表 - 实体类 * * @author zhanghp * @date 2023/6/25 14:50 */ @Data @NoArgsConstructor @AllArgsConstructor public class Demo implements Serializable { private Integer id; private String name; private Integer age; }
-
DemoMapper.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.zhanghp.dao.pojo.Demo"> <!-- 查询全部 --> <select id="selectDemo" resultType="demo"> select * from demo </select> <!-- 查询单个 --> <select id="selectOneRecord" resultType="demo"> select * from demo where id = 1 </select> </mapper>
-
UT测试用例
DemoTest.java
/** * @author zhanghp * @date 2023/6/25 15:00 */ public class DemoTest { @Test public void testConnectAndSelectOneRecord(){ final ConnectionDBWithXml connectionDBWithXml = new ConnectionDBWithXml(); connectionDBWithXml.connect(); } }
-
结果打印
Demo(id=1, name=张三, age=18) --------------------------------- Demo(id=1, name=张三, age=18) Demo(id=2, name=李四, age=20)
三. XML映射器
前置代码
ConnectUtil.java
抽离SqlSession实例化,及释放,方便外部类调用
/**
* 连接数据库工具类
*
* @author zhanghp
* @date 2023/6/25 23:53
*/
public class ConnectUtil {
/**
* xml配置路径
*/
private static final String RESOURCE = "mybatis-config.xml";
public ConnectUtil() {
}
/**
* 获取SqlSession
*
* @return {@link org.apache.ibatis.session.SqlSession}
*/
public static SqlSession getSqlSession() {
InputStream resourceAsStream = null;
try {
// MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。
resourceAsStream = Resources.getResourceAsStream(RESOURCE);
} catch (IOException e) {
e.printStackTrace();
}
// 初始化SqlSessionFactory:连接数据库的工厂类
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 增删改需要手动提交事务
// openSession(true);设置是否自动提交事务,true为自动提交,false为不自动提交(例子:修改方法)
// Spring整合后,就不在需要手动处理事务
return sqlSessionFactory.openSession(true);
}
/**
* 释放资源
*
* @param session {@link org.apache.ibatis.session.SqlSession}
*/
public static void release(SqlSession session) {
session.close();
}
}
3.1 select
3.1.1 元素属性
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType | 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。 |
resultType | 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。 |
resultMap | 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。 |
flushCache | 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。 |
useCache | 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。 |
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 | 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。 |
3.1.2 无参查询
1. 查询全部记录,通过List形式回参
DemoMapper.xml
<!-- 查询全部 -->
<select id="selectDemo" resultType="demo">
select *
from demo
</select>
SelectDemo.java
/**
* 获取全部记录,通过List返回
*/
public void queryAll() {
try {
List<Demo> demoList = SQLSESSION.selectList("selectDemo");
if (IterUtil.isNotEmpty(demoList)) {
demoList.forEach(System.out::println);
}
} finally {
// 释放资源
ConnectUtil.release(SQLSESSION);
}
}
UT测试用例 - SelectTest.java
@Test
public void queryAll() {
// select * from demo
SELECT_DEMO.queryAll();
}
2. 查询全部记录,通过Map形式回参
DemoMapper.xml
<!-- 查询全部 返回Map集合 -->
<select id="queryReturnMap" resultType="map">
select *
from demo
</select>
SelectDemo.java
/**
* 获取全部记录,通过Map返回
*/
public void queryReturnMap() {
try {
// 第二参数为mapKey:需指定主键名称
Map<Integer, Demo> demoMap = SQLSESSION.selectMap("queryReturnMap", "id");
if (MapUtil.isNotEmpty(demoMap)) {
demoMap.entrySet().forEach(it -> {
System.out.println(it.getKey() + ":" + it.getValue());
});
}
} finally {
// 释放资源
ConnectUtil.release(SQLSESSION);
}
}
UT测试用例 - SelectTest.java
@Test
public void queryReturnMap() {
// sql: select * from demo
SELECT_DEMO.queryReturnMap();
}
3.1.3 传参查询
1.单个参数类型传参
DemoMapper.xml
<!-- 通过主键id查询单个记录 -->
<select id="queryById" parameterType="int" resultType="demo">
select * from demo where id = #{id}
</select>
SelectDemo.java
/**
* select demo
*
* @author zhanghp
* @date 2023/6/25 23:30
*/
public class SelectDemo {
/**
* 获取SqlSession
*/
private final SqlSession SQLSESSION = ConnectUtil.getSqlSession();
/**
* 通过主键id传参获取对应的一条记录
*
* @param id 主键id
*/
public void queryById(Integer id) {
try {
// select
Demo demo = SQLSESSION.selectOne("queryById", 1);
System.out.println(demo);
} finally {
// 释放资源
ConnectUtil.release(SQLSESSION);
}
}
}
UT测试用例 - SelectTest.java
/**
* @author zhanghp
* @date 2023/6/26 0:04
*/
public class SelectTest {
private final SelectDemo SELECT_DEMO = new SelectDemo();
@Test
public void queryById() {
SELECT_DEMO.queryById(1);
}
}
2.多个基础数据类型map集合传参
DemoMapper.xml
<!-- 通过map查询匹配记录集合 -->
<select id="queryByMap" parameterType="map" resultType="demo">
select * from demo where name = #{name} and age= #{age}
</select>
SelectDemo.java
/**
* 通过map传参获取记录集合
*
* @param map {@link java.util.Map}
*/
public void queryByMap(Map<String, Object> map) {
try {
// select
List<Demo> demoList = SQLSESSION.selectList("queryByMap", map);
if (IterUtil.isNotEmpty(demoList)) {
demoList.forEach(System.out::println);
}
} finally {
// 释放资源
ConnectUtil.release(SQLSESSION);
}
}
UT测试用例 - SelectTest.java
@Test
public void queryByMap(){
Map<String, Object> map = new HashMap<>();
map.put("name", "张三");
map.put("age", 18);
SELECT_DEMO.queryByMap(map);
}
3.引用类型传参
DemoMapper.xml
<!-- 通过对象demo查询匹配记录集合 -->
<select id="queryByDemo" parameterType="demo" resultType="demo">
select * from demo where age= #{age}
</select>
SelectDemo.java
/**
* 通过对象传参获取集合
*
* @param demo {@link com.zhanghp.dao.pojo.Demo}
*/
public void queryByDemo(Demo demo) {
try {
List<Demo> demoList = SQLSESSION.selectList("queryByDemo", demo);
if (IterUtil.isNotEmpty(demoList)) {
demoList.forEach(System.out::println);
}
} finally {
// 释放资源
ConnectUtil.release(SQLSESSION);
}
}
UT测试用例 - SelectTest.java
@Test
public void queryByDemo(){
final Demo demo = new Demo();
demo.setAge(20);
SELECT_DEMO.queryByDemo(demo);
}
3.2 insert, update&delete
前置
insert update&delete需要手动提交事务,来进行对数据库记录的操作,设置如下
ConnectUtil.java
// 增删改需要手动提交事务
// openSession(true);设置是否自动提交事务,true为自动提交,false为不自动提交(例子:修改方法)
// Spring整合后,就不在需要手动处理事务
// 或者不设置true,当执行完增删改,可使用sqlSession.commit(),手动提交事务
sqlSessionFactory.openSession(true);
3.2.1 元素属性
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
parameterType | 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。 |
flushCache | 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。 |
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 的语句;如果带和不带的语句都有,则不带的会被忽略。 |
3.2.2 insert
DemoMapper.xml
<!-- 插入一条记录 -->
<insert id="addOne">
insert into demo
values (100, '测试', 30)
</insert>
<!-- 通过对象传参,插入一条记录 -->
<insert id="addOneByDemo" parameterType="demo">
insert into demo
values (#{id}, #{name}, #{age})
</insert>
<!-- 通过对象传参,使用主键自增,插入一条记录 -->
<insert id="addOneByDemoUseGenerateKey" parameterType="demo" useGeneratedKeys="true" keyProperty="id">
insert into demo
values (#{id}, #{name}, #{age})
</insert>
InsertDemo.java
/**
* 插入
*
* @author zhanghp
* @date 2023/6/26 8:31
*/
public class InsertDemo {
/**
* 获取SqlSession
*/
private final SqlSession SQLSESSION = ConnectUtil.getSqlSession();
/**
* 插入固定数据
*/
public void addOne() {
try{
final int rows = SQLSESSION.insert("addOne");
System.out.println("插入成功的行数:" + rows);
}finally {
ConnectUtil.release(SQLSESSION);
}
}
/**
* 通过对象作为参数传递,新增一条记录
*/
public void addOneByDemo(Demo demo) {
try{
final int rows = SQLSESSION.insert("addOneByDemo", demo);
System.out.println("插入成功的行数:" + rows);
}finally {
ConnectUtil.release(SQLSESSION);
}
}
/**
* 通过对象作为参数传递,使用数据库主键自增策略,实现新增一条记录
*/
public void addOneByDemoUseGenerateKey(Demo demo) {
try{
final int rows = SQLSESSION.insert("addOneByDemo", demo);
System.out.println("插入成功的行数:" + rows);
}finally {
ConnectUtil.release(SQLSESSION);
}
}
}
UT测试用例 - InsertTest.java
/**
* @author zhanghp
* @date 2023/6/26 8:34
*/
public class InsertTest {
private final InsertDemo insertDemo = new InsertDemo();
@Test
public void addOne(){
// sql: insert into demo values (100, '测试', 30)
insertDemo.addOne();
}
@Test
public void addOneByDemo(){
// sql: insert into demo values (4, '小胡', 30)
insertDemo.addOneByDemo( new Demo(4, "小胡", 30));
}
@Test
public void addOneByDemoUseGenerateKey(){
// sql: insert into demo values (null, '小李', 30)
insertDemo.addOneByDemoUseGenerateKey( new Demo(null, "小李", 40));
}
}
3.2.3 update
DemoMapper.xml
<!-- 更新一条记录 -->
<update id="updateOne" parameterType="demo">
update demo
set name = '小陈',
age = 18
where id = 1
</update>
<!-- 通过对象传参,更新记录 -->
<update id="updateByDemo" parameterType="demo">
update demo
set name =#{name},
age = #{age}
where id = #{id}
</update>
UpdateDemo.java
/**
* 更新
*
* @author zhanghp
* @date 2023/6/26 8:31
*/
public class UpdateDemo {
/**
* 获取SqlSession
*/
private final SqlSession SQLSESSION = ConnectUtil.getSqlSession();
/**
* 固定更新一条数据
*/
public void updateOne() {
try {
final int rows = SQLSESSION.insert("updateOne");
System.out.println("更新成功的行数:" + rows);
} finally {
ConnectUtil.release(SQLSESSION);
}
}
/**
* 通过传入的对象,通过主键,更新其余的属性
*/
public void updateByDemo(Demo demo) {
try {
final int rows = SQLSESSION.update("updateByDemo", demo);
System.out.println("更新成功的行数:" + rows);
} finally {
ConnectUtil.release(SQLSESSION);
}
}
}
UT测试用例 - UpdateTest.java
/**
* @author zhanghp
* @date 2023/6/26 8:34
*/
public class UpdateTest {
private final UpdateDemo updateDemo = new UpdateDemo();
@Test
public void updateOne() {
// sql: update demo set name = '小陈', age = 18 where id = 1
updateDemo.updateOne();
}
@Test
public void updateByDemo(){
// sql: update demo set name = '小胡', age = 50 where id = 4
updateDemo.updateByDemo(new Demo(4, "小胡", 50));
}
}
3.2.4 delete
DemoMapper.xml
<!-- 删除一条记录 -->
<delete id="deleteOne">
delete from demo where id = 101
</delete>
<!-- 通过主键id删除一条记录 -->
<delete id="deleteOneById" parameterType="int">
delete from demo where id = #{id}
</delete>
DeleteDemo.java
/**
* 删除
*
* @author zhanghp
* @date 2023/6/26 8:31
*/
public class DeleteDemo {
/**
* 获取SqlSession
*/
private final SqlSession SQLSESSION = ConnectUtil.getSqlSession();
/**
* 删除固定一条记录
*/
public void deleteOne() {
try{
final int rows = SQLSESSION.delete("deleteOne");
System.out.println("删除成功的行数:" + rows);
}finally {
ConnectUtil.release(SQLSESSION);
}
}
/**
* 通过主键id删除固定一条记录
*/
public void deleteOneById(Integer id) {
try{
final int rows = SQLSESSION.delete("deleteOneById", id);
System.out.println("删除成功的行数:" + rows);
}finally {
ConnectUtil.release(SQLSESSION);
}
}
}
UT测试用例 - DeleteTest.java
/**
* @author zhanghp
* @date 2023/6/26 8:34
*/
public class DeleteTest {
private final DeleteDemo deleteDemo = new DeleteDemo();
@Test
public void deleteOne(){
// sql: delete from demo where id = 101
deleteDemo.deleteOne();
}
@Test
public void deleteOneById(){
// sql: delete from demo where id = 100
deleteDemo.deleteOneById(100);
}
}
3.3 ResultMap - 关联查询
ResultMap里的标签
属性 | 功能 |
---|---|
constructor | 用于在实例化类时,注入结果到构造方法中 |
association | 一个复杂类型的关联(一对一) |
collection | 一个复杂类型的集合(一对多) |
discriminator | 使用结果值来决定使用哪个 resultMap |
resultmap简单使用
resultMapSqlDemo.java
/**
* @author zhanghp
* @date 2023/6/27 8:45
*/
public class ResultMapSqlDemo {
private final String ENVIRONMENT = "deep_practice";
private final SqlSession SQLSESSION = ConnectOptionalEnvironmentUtil.getSqlSession(ENVIRONMENT);
/**
* resultMap基本使用
*/
public void queryResultMap() {
try {
// test1 : MyBatis 会在幕后自动创建一个 ResultMap,若属性名和列名不匹配,会返回null
final List<Emp> results1 = SQLSESSION.selectList("queryResultType");
CommonUtil.printList(results1);
// test2 : resultMap 映射后,会重新自动转换名称不一致问题
final List<Emp> results2 = SQLSESSION.selectList("queryResultMap");
CommonUtil.printList(results2);
} finally {
ConnectUtil.release(SQLSESSION);
}
}
}
mapper/EmpMapper.xml
<resultMap id="deptResultMap" type="dept">
<id property="deptno" column="DEPTNO"></id>
<result property="dname" column="DNAME"></result>
<result property="loc" column="LOC"></result>
</resultMap>
<resultMap id="empResultMap" type="emp">
<id property="empno" column="EMPNO"/>
<result property="name" column="ENAME"/>
<result property="job" column="JOB"/>
<result property="mgr" column="MGR"/>
<result property="hiredate" column="HIREDATE"/>
<result property="sal" column="SAL"/>
<result property="comm" column="COMM"/>
<result property="deptno" column="DEPTNO"/>
</resultMap>
<!-- 查询全部 resultType -->
<select id="queryResultType" resultType="emp">
select *
from emp
</select>
<!-- 查询全部 resultMap-->
<select id="queryResultMap" resultMap="empResultMap">
select *
from emp
</select>
UT用例 - ResultMapSqlTest.java
/**
* @author zhanghp
* @date 2023/7/2 21:47
*/
public class ResultMapSqlTest {
private final ResultMapSqlDemo DEMO = new ResultMapSqlDemo();
@Test
public void queryResultMap(){
DEMO.queryResultMap();
}
}
1. constructor
用于在实例化类时,注入结果到构造方法中
可以这么理解,通过类里写的有参构造器,来进行参数的传递
ps:
要注意构造器入参的顺序,顺序和constructor指定的属性名顺序不同会导致数据为空
属性 | 功能 |
---|---|
idArg | ID 参数;标记出作为 ID 的结果可以帮助提高整体性能 |
arg | 将被注入到构造方法的一个普通结果 |
ResultMapSqlDemo.java
/**
* 通过<constructor/>元素查询
*/
public void queryConstructor() {
try {
// test1 : MyBatis 会在幕后自动创建一个 ResultMap,若属性名和列名不匹配,会返回null
final List<Dept> results = SQLSESSION.selectList("queryConstructor");
CommonUtil.printList(results);
} finally {
ConnectUtil.release(SQLSESSION);
}
}
Dept.java
/**
* @author zhanghp
* @date 2023/6/28 9:25
*/
@Data
@ToString
@NoArgsConstructor
public class Dept implements Serializable {
private Integer deptno;
private String dname;
private String loc;
// 组合一个Emp的List集合作为属性
private List<Emp> empList;
public Dept(Integer deptno, String dname, List<Emp> empList) {
this.deptno = deptno;
this.dname = dname;
this.empList = empList;
}
public Dept(Integer deptno, String dname, String loc) {
this.deptno = deptno;
this.loc = loc;
this.dname = dname;
}
}
mappers/DeptMapp.xml
<resultMap id="deptResultMap" type="dept">
<!-- 按照有参构造器,依次注入,若参数顺序不一致,可通过构造器调整参数位置,来达到效果-->
<constructor>
<idArg column="deptno" javaType="Integer"/>
<arg column="dname" javaType="String"/>
<!-- 通过deptno传参,传入constructorQuery方法,来进行参数-->
<arg select="com.zhanghp.dao.mapper.EmpMapper.constructorQuery" javaType="List" column="deptno" />
</constructor>
<result property="loc" column="LOC"/>
</resultMap>
<!-- 查询全部 通过resultMap constructor标签 -->
<select id="queryConstructor" resultMap="deptResultMap">
select *
from dept
</select>
UT用例 - ResultMapSql.java
@Test
public void queryConstructor(){
DEMO.queryConstructor();
}
2. association(一对一)
关联(association)元素处理“有一个”类型的关系。 比如,在我们的示例中,一个博客有一个用户。关联结果映射和其它类型的映射工作方式差不多。 你需要指定目标属性名以及属性的javaType
(很多时候 MyBatis 可以自己推断出来),在必要的情况下你还可以设置 JDBC 类型,如果你想覆盖获取结果值的过程,还可以设置类型处理器。
关联的不同之处是,你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:
- 嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
- 嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。
resultMapSqlDemo.java
/**
* 一对一
*/
public void queryAssociation() {
try {
final List<Emp> results1 = SQLSESSION.selectList("queryAssociation");
CommonUtil.printList(results1);
} finally {
ConnectUtil.release(SQLSESSION);
}
}
/**
* 一对一
* 引用select,引用外部的id
*/
public void queryAssociation2() {
try {
// association对应的select属性查询dept表,是一个一个sql查询,比较消耗时间
final List<Emp> results1 = SQLSESSION.selectList("queryAssociation2");
CommonUtil.printList(results1);
} finally {
ConnectUtil.release(SQLSESSION);
}
}
/**
* 一对一
* 引用select,引用内部的id
*/
public void queryAssociation3() {
try {
// association对应的select属性查询dept表,是一个一个sql查询,比较消耗时间
final List<Emp> results1 = SQLSESSION.selectList("queryAssociation3");
CommonUtil.printList(results1);
} finally {
ConnectUtil.release(SQLSESSION);
}
}
mapper/EmpMapper.xml
<!-- 一对一查询 -->
<resultMap id="oneToOne" type="emp">
<id property="empno" column="EMPNO"/>
<result property="name" column="ENAME"/>
<result property="job" column="JOB"/>
<result property="mgr" column="MGR"/>
<result property="hiredate" column="HIREDATE"/>
<result property="sal" column="SAL"/>
<result property="comm" column="COMM"/>
<result property="deptno" column="DEPTNO"/>
<association property="deptObject" javaType="dept" resultMap="deptResultMap"/>
</resultMap>
<resultMap id="oneToOne2" type="emp">
<id property="empno" column="EMPNO"/>
<result property="name" column="ENAME"/>
<result property="job" column="JOB"/>
<result property="mgr" column="MGR"/>
<result property="hiredate" column="HIREDATE"/>
<result property="sal" column="SAL"/>
<result property="comm" column="COMM"/>
<result property="deptno" column="DEPTNO"/>
<association property="deptObject" javaType="dept" column="DEPTNO"
select="com.zhanghp.dao.mapper.DeptMapper.queryByDeptno"/>
</resultMap>
<resultMap id="oneToOne3" type="emp">
<id property="empno" column="EMPNO"/>
<result property="name" column="ENAME"/>
<result property="job" column="JOB"/>
<result property="mgr" column="MGR"/>
<result property="hiredate" column="HIREDATE"/>
<result property="sal" column="SAL"/>
<result property="comm" column="COMM"/>
<result property="deptno" column="DEPTNO"/>
<association property="deptObject" javaType="dept" column="DEPTNO" select="queryDeptByDeptno"/>
</resultMap>
<select id="queryDeptByDeptno" resultMap="deptResultMap">
select *
from dept
where DEPTNO = #{deptno}
</select>
<select id="queryAssociation" resultMap="oneToOne">
select *
from emp e
left join dept d on (e.DEPTNO = d.DEPTNO)
</select>
<select id="queryAssociation2" resultMap="oneToOne2">
select *
from emp
</select>
<select id="queryAssociation3" resultMap="oneToOne3">
select *
from emp
</select>
UT用例 - ResultMapSqlTest.java
@Test
public void oneToOne(){
DEMO.queryAssociation();
DEMO.queryAssociation2();
DEMO.queryAssociation3();
}
3.collection(一对多)
resultMapSqlDemo.java
/**
* 一对多查询
*/
public void queryOneToMulti() {
try {
final List<Dept> results1 = SQLSESSION.selectList("queryOneToMulti");
CommonUtil.printList(results1);
} finally {
ConnectUtil.release(SQLSESSION);
}
}
mapper/DeptMapper.xml
<!-- 一对多查询 -->
<resultMap id="oneToMulti" type="dept">
<id property="deptno" column="DEPTNO"></id>
<result property="dname" column="DNAME"></result>
<result property="loc" column="LOC"></result>
<collection property="empList" ofType="emp">
<id property="empno" column="EMPNO"/>
<result property="name" column="ENAME"/>
<result property="job" column="JOB"/>
<result property="mgr" column="MGR"/>
<result property="hiredate" column="HIREDATE"/>
<result property="sal" column="SAL"/>
<result property="comm" column="COMM"/>
<result property="deptno" column="DEPTNO"/>
</collection>
</resultMap>
<select id="queryOneToMulti" resultMap="oneToMulti">
select * from dept d left join emp e on (d.DEPTNO = e.DEPTNO)
</select>
UT用例 - ResultMapSqlTest.java
@Test
public void oneToMulti(){
DEMO.queryOneToMulti();
}
4. discriminator
有时候,一个数据库查询可能会返回多个不同的结果集(但总体上还是有一定的联系的)。 鉴别器(discriminator)元素就是被设计来应对这种情况的,另外也能处理其它情况,例如类的继承层次结构。 鉴别器的概念很好理解——它很像 Java 语言中的 switch 语句。
用的场景较少,没写相关示例,我这里查了相关文章示例,有需要的请自行观看
官网例子
https://blog.csdn.net/weixin_45799972/article/details/121657855
https://www.jianshu.com/p/e808b1cfb265
5. 多对多查询
Sql表准备
CREATE TABLE `project` (
`pid` int NOT NULL AUTO_INCREMENT,
`pname` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`money` int DEFAULT NULL,
PRIMARY KEY (`pid`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;
CREATE TABLE `project_record` (
`empno` int NOT NULL,
`pid` int NOT NULL,
PRIMARY KEY (`empno`,`pid`) USING BTREE,
KEY `fk_project_pro` (`pid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;
实例查询原理:
一个project对应多个project_record,而每个project_record记录都对应一个emp表
resultMapSqlDemo.java
/**
* 多对多查询
*/
public void queryMultiToMulti() {
try {
final List<Project> results1 = SQLSESSION.selectList("queryMultiToMulti");
CommonUtil.printList(results1);
} finally {
ConnectUtil.release(SQLSESSION);
}
}
mapper/ProjectMapper.xml
<!-- 多对多查询 -->
<resultMap id="empResultMap" type="emp">
<id property="empno" column="EMPNO"/>
<result property="name" column="ENAME"/>
<result property="job" column="JOB"/>
<result property="mgr" column="MGR"/>
<result property="hiredate" column="HIREDATE"/>
<result property="sal" column="SAL"/>
<result property="comm" column="COMM"/>
<result property="deptno" column="DEPTNO"/>
</resultMap>
<resultMap id="multiToMulti" type="project">
<id property="pid" column="pid"/>
<result property="pname" column="pname"/>
<result property="money" column="money"/>
<collection property="projectRecords" ofType="projectRecord">
<!-- 多主键必须都写id属性,否则查询的结果会是一对一-->
<id property="pid" column="pid"/>
<id property="empno" column="empno"/>
<association property="empObject" javaType="emp" resultMap="empResultMap"/>
</collection>
</resultMap>
<select id="queryMultiToMulti" resultMap="multiToMulti">
select *
from project p
left join project_record pr on (p.pid = pr.pid)
left join emp e on (pr.empno = e.empno)
</select>
UT用例 - ResultMapSqlTest.java
@Test
public void multiToMulti(){
DEMO.queryMultiToMulti();
}
3.4 存储过程调用
sql准备
CREATE DEFINER=`root`@`localhost` PROCEDURE `getEmpAndDept`(IN deptno int(11))
BEGIN
select * from emp e left join dept d on (e.deptno = d.deptno) where e.deptno = deptno;
END
resultMapSqlDemo.java
/**
* 调用存储过程
*/
public void callProcedure(Integer deptno) {
try {
// association对应的select属性查询dept表,是一个一个sql查询,比较消耗时间
final List<Emp> results1 = SQLSESSION.selectList("callProcedure", deptno);
CommonUtil.printList(results1);
} finally {
ConnectUtil.release(SQLSESSION);
}
}
mapper/EmpMapper.xml
jdbcType:指定数据库该字段类型,mybatis包含的jdbcType请自行上官网查看(在XML配置-结果映射章节)
mode:
IN
,OUT
或INOUT
参数。如果参数的mode
为OUT
或INOUT
,将会修改参数对象的属性值,以便作为输出参数返回。 如果mode
为OUT
(或INOUT
),而且jdbcType
为CURSOR
(也就是 Oracle 的 REFCURSOR),你必须指定一个resultMap
引用来将结果集ResultMap
映射到参数的类型上。要注意这里的javaType
属性是可选的,如果留空并且 jdbcType 是CURSOR
,它会被自动地被设为ResultMap
。
<!-- 调用存储过程 -->
<select id="callProcedure" resultMap="oneToOne" statementType="CALLABLE">
{call getEmpAndDept(#{dept,jdbcType=INTEGER,mode=IN})}
</select>
UT用例 - ResultMapSqlTest.java
@Test
public void callProcedure(){
DEMO.callProcedure(20);
}
四. Mybatis 代理模式开发
4.1 简介
传统的XML配置映射器的方式,实现对数据库的增删改查,是基于SqlSession实例中的Api进行完成,虽说基需都能满足,但还是有很多劣势
- selectOne(),selectAll(),seletMap(),update(),insert(),delete()等,都是基于SqlSession完成,规定的传参格式很单一,若传参多个参数,需要集成到map,对象等。
- 由xml配置,需要额外实例化SqlSession,而没直接提供数据库操作接口,不利于后期维护
Mybatis的代理模式开发,通过Mapper代理(或称接口绑定)的操作方式,相对传统的XML映射器,具有以下优点
- 接口规范统一,方便模块间的调用,利于维护
- 传参多样化,不拘束于传统单一的传参方式
4.2 代理接口编写
4.2.1 步骤流程
- 创建DemoMapper.java:用于Mapper接口代理开发,统一访问该类,调用api实现对数据库的增删改查
- 创建DemoMapper.xml(和DemoMapper.java同路径包下):用于sql的编写
- mybatis-config.xml增加对com.zhanghp.dao.mapper包路径下文件的扫描配置
这里需要注意几个点
Mapper接口绑定类的名称,需和映射文件名称保持一致(不包含拓展名)
Mapper映射文件的namespace为Mapper接口的全路径包名
sql语句对应的id,需和Mapper接口的方法名保持一致
Mapper接口和Mapper映射文件编译后会在target的同一目录下
ps1:
拥有两个DemoMapper.xml文件,并不冲突,这里说明一下:
- 包路径不同
- namespace的作用域对象不同,一个是作用的是Demo类,另一个作用的是DemoMapper接口类
ps2:
创建映射文件包路径com.zhanghp.dao.mapper时,需一个一个名称的文件夹创建,若直接文件夹名写成com.zhanghp.dao.mapper,并回车后,会出现找不到该包的异常,这里需要注意一下。
4.2.2 代码实现
目录结构
这里读者会发现,笔者创建的DemoMapper.java和DemoMapper.xml前有2个小图标,这里读者若也想实现该效果
请自行去"settings" -> “plugins”,搜索MyBatisX插件,下载并重启idea即可复现图标效果
-
创建一个接口类DemoMapper.java
/** * @author zhanghp * @date 2023/6/26 10:45 */ public interface DemoMapper { }
-
创建DemoMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace 写Mapper接口的全路径包名 --> <mapper namespace="com.zhanghp.dao.mapper.DemoMapper"> </mapper>
-
mybatis-config.xml添加配置
<mappers> <!-- 扫描xml路径 --> <mapper resource="mappers/DemoMapper.xml"/> <!-- 扫描代理接口模式类包路径 --> <package name="com.zhanghp.dao.mapper"></package> </mappers>
4.2 crud代码实现
所用到的CommonUtil.java代码
上面xml映射器的代码,一个一个手动写代码,打印集合数据,很费时间,这里笔者写了一个公共类,来进行统一编写实现List,Map的集合打印
/**
* @author zhanghp
* @date 2023/6/26 13:31
*/
public class CommonUtil {
/**
* List打印
*
* @param list 集合
*/
public static void printList(List<?> list) {
if (IterUtil.isNotEmpty(list)) {
list.forEach(System.out::println);
}
}
/**
* Map打印
*
* @param map 集合
*/
public static void printMap(Map<?, ?> map) {
if (MapUtil.isNotEmpty(map)) {
map.entrySet().forEach(it -> {
System.out.println(it.getKey() + ":" + it.getValue());
});
}
}
}
DemoMapper.java
/**
* @author zhanghp
* @date 2023/6/26 10:45
*/
public interface DemoMapper {
/**
* 查询全部记录
*
* @return {@link com.zhanghp.dao.pojo.Demo}
*/
List<Demo> queryAll();
/**
* 通过Demo对象传参,新增一条记录
*
* @param demo 所需插入的对象信息
* @return 插入成功的行数
*/
int insertOneByDemo(@Param("demo") Demo demo);
/**
* 通过Demo对象的传参及指定的id,来进行更新记录
*
* @param demo 所需修改的对象信息
* @return 修改成功的行数
*/
int updateOneByDemo(@Param("demo") Demo demo);
/**
* 根据主键id删除指定的记录
*
* @param id 主键id
* @return 删除成功的行数
*/
int deleteOneById(@Param("id") Integer id);
}
DemoMapper.xml(com.zhanghp.dao.mapper路径下)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace 写Mapper接口的全路径包名 -->
<mapper namespace="com.zhanghp.dao.mapper.DemoMapper">
<!-- 查询全部数据记录 -->
<select id="queryAll" resultType="demo">
select * from demo
</select>
<!-- 通过对象传参,新增记录 -->
<insert id="insertOneByDemo" parameterType="demo" useGeneratedKeys="true" keyProperty="id">
insert into demo values (#{demo.id}, #{demo.name}, #{demo.age})
</insert>
<!-- 通过对象传参,更新记录 -->
<update id="updateOneByDemo" parameterType="demo">
update demo
set name =#{demo.name},
age = #{demo.age}
where id = #{demo.id}
</update>
<!-- 通过主键id删除一条记录 -->
<delete id="deleteOneById" parameterType="int">
delete
from demo
where id = #{id}
</delete>
</mapper>
SelectMapperDemo.java
/**
* @author zhanghp
* @date 2023/6/26 13:30
*/
public class SelectMapperDemo {
private final SqlSession SQLSESSION = ConnectUtil.getSqlSession();
private final DemoMapper MAPPER = SQLSESSION.getMapper(DemoMapper.class);
public void queryAll() {
CommonUtil.printList(MAPPER.queryAll());
}
public void insertOneByDemo(Demo demo) {
System.out.println(MAPPER.insertOneByDemo(demo));
}
public void updateOneByDemo(Demo demo) {
System.out.println(MAPPER.updateOneByDemo(demo));
}
public void deleteOneById(Integer id) {
System.out.println(MAPPER.deleteOneById(id));
}
}
UT测试用例 - SelectMapperTest.java
/**
* @author zhanghp
* @date 2023/6/26 13:35
*/
public class SelectMapperTest {
private final SelectMapperDemo DEMO = new SelectMapperDemo();
@Test
public void queryAll(){
// select * from demo
DEMO.queryAll();
}
@Test
public void insertOneByDemo(){
// insert into demo values (null, '小刘', 22)
DEMO.insertOneByDemo(new Demo(null, "小刘", 22));
}
@Test
public void updateOneByDemo(){
// update demo set name = '小贺', age = '11' where id = 1
DEMO.updateOneByDemo(new Demo(1, "小贺", 11));
}
@Test
public void deleteOneById() {
// delete from demo where id = 1
DEMO.deleteOneById(1);
}
}
4.3 深入查询
4.3.1 传参
1. 通过单个基本数据类型查询
SelectByParamDemo.java
/**
* 参数传递的几种查询方式
*
* @author zhanghp
* @date 2023/6/26 21:09
*/
public class SelectByParamMapperDemo {
private final SqlSession SQLSESSION = ConnectUtil.getSqlSession();
private final DemoMapper MAPPER = SQLSESSION.getMapper(DemoMapper.class);
/**
* 通过单个基本数据类型查询
*
* @param name 姓名
*/
public void selectBySingleType(String name) {
final List<Demo> demos = MAPPER.selectBySingleType(name);
CommonUtil.printList(demos);
}
}
DemoMapper.xml
<select id="selectBySingleType" parameterType="string" resultType="demo">
select * from demo where name = #{name}
</select>
UT测试用例 - SelectByParamMapperTest.java
/**
* @author zhanghp
* @date 2023/6/26 23:22
*/
public class SelectByParamMapperTest {
private final SelectByParamMapperDemo DEMO= new SelectByParamMapperDemo();
@Test
public void selectBySingleType(){
DEMO.selectBySingleType("小白");
}
}
2. 通过多个基本数据类型查询
SelectByParamDemo.java
/**
* 通过多个基本数据类型查询
*
* @param name 姓名
* @param age 年龄
*/
public void selectByMultiType(String name, Integer age) {
final List<Demo> demos = MAPPER.selectByMultiType(name, age);
CommonUtil.printList(demos);
}
DemoMapper.xml
<select id="selectByMultiType" resultType="demo">
select * from demo where name = #{name} and age = #{age}
</select>
UT测试用例 - SelectByParamMapperTest.java
@Test
public void selectByMultiType(){
DEMO.selectByMultiType("小白", 10);
}
3. 通过实体类查询
SelectByParamDemo.java
/**
* 通过实体类查询
*
* @param demo 实体类
*/
public void selectByBean(Demo demo) {
final List<Demo> demos = MAPPER.selectByBean(demo);
CommonUtil.printList(demos);
}
DemoMapper.xml
<select id="selectByBean" parameterType="demo" resultType="demo">
select * from demo where name = #{demo.name} and age = #{demo.age}
</select>
UT测试用例 - SelectByParamMapperTest.java
@Test
public void selectByBean(){
DEMO.selectByBean(new Demo(null, "小白", 10));
}
4. 通过多个实体类查询
SelectByParamDemo.java
/**
* 通过多个实体类查询
*
* @param demo01 实体类1
* @param demo02 实体类2
*/
public void selectByMultiBean(Demo demo01, Demo demo02) {
final List<Demo> demos = MAPPER.selectByMultiBean(demo01, demo02);
CommonUtil.printList(demos);
}
DemoMapper.xml
<select id="selectByMultiBean" resultType="demo">
select * from demo where name = #{demo01.name} and age = #{demo02.age}
</select>
UT测试用例 - SelectByParamMapperTest.java
@Test
public void selectByMultiBean(){
DEMO.selectByMultiBean(new Demo(null, "小白", null),
new Demo(null, null, 10));
}
5. 通过map集合查询
SelectByParamDemo.java
/**
* 通过map集合查询
*
* @param map map集合
*/
public void selectByMap(Map<String, Object> map) {
final List<Demo> demos = MAPPER.selectByMap(map);
CommonUtil.printList(demos);
}
DemoMapper.xml
<select id="selectByMap" parameterType="map" resultType="demo">
select * from demo where name = #{map.name} and age = #{map.age}
</select>
UT测试用例 - SelectByParamMapperTest.java
@Test
public void selectByMap(){
Map<String, Object> map = new HashMap<String, Object>(){{
put("name", "小白");
put("age", 10);
}};
DEMO.selectByMap(map);
}
4.3.2 模糊查询
通过concat函数,进行实现模糊查询
FuzzySelectMapperDemo
/**
* 模糊查询
*
* @author zhanghp
* @date 2023/6/26 21:45
*/
public class FuzzySelectMapperDemo {
private final SqlSession SQLSESSION = ConnectUtil.getSqlSession();
private final DemoMapper MAPPER = SQLSESSION.getMapper(DemoMapper.class);
public void fuzzyQuery(String fuzzyName){
final List<Demo> demos = MAPPER.fuzzyQuery(fuzzyName);
CommonUtil.printList(demos);
}
}
DemoMapper.xml
<select id="fuzzyQuery" parameterType="string" resultType="demo">
select * from demo where name like concat('%', #{name}, '%')
</select>
UT测试用例 - FuzzySelectMapperTest
/**
* @author zhanghp
* @date 2023/6/26 23:09
*/
public class FuzzySelectMapperTest {
private final FuzzySelectMapperDemo DEMO = new FuzzySelectMapperDemo();
@Test
public void fuzzyQuery(){
DEMO.fuzzyQuery("小");
}
}
4.4 动态sql
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用
4.4.1 if
判断字符串:1. 不为null 2. 不为空字符串
判断对象里的String类型属性:1. 对象不为空 2. 对象里的属性不为null 且 不是空字符串
DynamicSqlDemo.java
/**
* @author zhanghp
* @date 2023/6/27 18:54
*/
public class DynamicSqlDemo {
private final SqlSession SQLSESSION = ConnectUtil.getSqlSession();
private final DemoMapper MAPPER = SQLSESSION.getMapper(DemoMapper.class);
public void dynamicIf() {
try {
// test1 select * from demo where 1=1 and name = ?
CommonUtil.printList(MAPPER.dynamicIf("小胡"));
// test2 select * from demo where 1=1
CommonUtil.printList(MAPPER.dynamicIf(null));
// test3 <if test = "name != null and name != ''">
// select * from demo where 1=1
CommonUtil.printList(MAPPER.dynamicIf(""));
} finally {
ConnectUtil.release(SQLSESSION);
}
}
public void dynamicIfByDemo() {
try {
// select * from demo where 1=1 and name = ?
CommonUtil.printList(MAPPER.dynamicIfByDemo(new Demo(null, "小胡", null)));
} finally {
ConnectUtil.release(SQLSESSION);
}
}
public void dynamicIfFuzzy() {
try {
// select * from demo where 1=1 and name like concat('%','小','%')
CommonUtil.printList(MAPPER.dynamicIfFuzzy("小"));
} finally {
ConnectUtil.release(SQLSESSION);
}
}
}
DemoMapper.xml
<select id="dynamicIf" parameterType="string" resultType="demo">
select * from demo
where 1=1
<if test="name != null and name != ''">
and name = #{name}
</if>
</select>
<select id="dynamicIfByDemo" parameterType="demo" resultType="demo">
select * from demo
where 1=1
<if test="demo != null and demo.name != null and demo.name != ''">
and name = #{demo.name}
</if>
</select>
<select id="dynamicIfFuzzy" parameterType="string" resultType="demo">
select * from demo
where 1=1
<if test="name != null">
and name like concat('%', #{name}, '%')
</if>
</select>
UT测试用例 - DynamicSqlTest.java
/**
* @author zhanghp
* @date 2023/6/27 19:07
*/
public class DynamicSqlTest {
private final DynamicSqlDemo DEMO = new DynamicSqlDemo();
@Test
public void dynamicIf(){
DEMO.dynamicIf();
}
@Test
public void dynamicIfByDemo(){
DEMO.dynamicIfByDemo();
}
@Test
public void dynamicIfFuzzy(){
DEMO.dynamicIfFuzzy();
}
}
4.4.2 choose, when&otherwise
choose:前面的when条件成立 后面的 when就不再判断了
DynamicSqlDemo.java
public void dynamicChooseWhenAndOtherwise() {
try {
// test1 select * from demo where 1=1 and age = ?
CommonUtil.printList(MAPPER.dynamicChooseWhenAndOtherwise(new Demo(null, null, 10), ""));
// test2 select * from demo where 1=1 and name = ?
CommonUtil.printList(MAPPER.dynamicChooseWhenAndOtherwise(new Demo(null, null, null), "小胡"));
// test3 select * from demo where 1=1 and name like concat('%','小','%')
CommonUtil.printList(MAPPER.dynamicChooseWhenAndOtherwise(new Demo(null, null, null), ""));
} finally {
ConnectUtil.release(SQLSESSION);
}
}
DemoMapper.xml
<select id="dynamicChooseWhenAndOtherwise" resultType="demo">
select * from demo where 1=1
<choose>
<when test="demo != null and demo.age != null">
and age = #{demo.age}
</when>
<when test="name != null and name != ''">
and name = #{name}
</when>
<otherwise>
and name like concat('%','小','%')
</otherwise>
</choose>
</select>
UT测试用例 - DynamicSqlTest.java
@Test
public void dynamicChooseWhenAndOtherwise(){
DEMO.dynamicChooseWhenAndOtherwise();
}
4.4.3 trim, where&set
where:where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
trim
<trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim>
属性 功能 prefix 增加的前缀 prefixOverrides 去除的前缀 suffix 增加的后缀 suffixOverrides 去除的后缀
DynamicSqlDemo.java
public void dynamicWhere() {
try {
// select * from demo
CommonUtil.printList(MAPPER.dynamicWhere(""));
} finally {
ConnectUtil.release(SQLSESSION);
}
}
public void dynamicTrim() {
try {
// select * from demo where 1=1
CommonUtil.printList(MAPPER.dynamicTrim());
} finally {
ConnectUtil.release(SQLSESSION);
}
}
public void dynamicSet() {
try {
// test1 update demo SET age = ? where id = ?
System.out.println(MAPPER.dynamicSet(11, 2));
//test2 报错:age=null 所以无法更新 update demo where id = 2
System.out.println(MAPPER.dynamicSet(null, 2));
} finally {
ConnectUtil.release(SQLSESSION);
}
}
DemoMapper.xml
<select id="dynamicWhere" parameterType="string" resultType="demo">
select * from demo
<where>
<if test="name != null and name != ''">
name = #{name}
</if>
</where>
</select>
<select id="dynamicTrim" resultType="demo">
select * from demo
<trim prefix="where">
1=1
</trim>
</select>
<update id="dynamicSet">
update demo
<set>
<if test="age != null">age = #{age}</if>
</set>
where id = #{id}
</update>
UT测试用例 - DynamicSqlTest.java
@Test
public void dynamicWhere(){
DEMO.dynamicWhere();
}
@Test
public void dynamicTrim(){
DEMO.dynamicTrim();
}
@Test
public void dynamicSet(){
DEMO.dynamicSet();
}
4.4.4 foreach
属性 | 功能 |
---|---|
collection | 遍历的集合或者是数组,参数是数组。collection中名字指定为array,参数是List集合。collection中名字指定为list |
separator | 遍历时,采用的分隔符 |
open | 以什么开头 |
close | 以什么结尾 |
item | 中间变量名 |
DynamicSqlDemo.java
public void dynamicForeach() {
try {
// select * from demo where id in ( ? , ? , ? )
CommonUtil.printList(MAPPER.dynamicForeach(Arrays.asList(1, 2, 3)));
} finally {
ConnectUtil.release(SQLSESSION);
}
}
DemoMapper.xml
<select id="dynamicForeach" parameterType="list" resultType="demo">
select * from demo
where id in
<foreach collection="list" item="it" separator="," open="(" close=")">
#{it}
</foreach>
</select>
UT测试用例 - DynamicSqlTest.java
@Test
public void dynamicForeach(){
DEMO.dynamicForeach();
}
4.4.5 sql&bind
sql:如果引用sql标签的话用的refid
bind
属性 功能 name 名称 value 要使用绑定的值,通过#{name}来使用
DynamicSqlDemo.java
public void dynamicSql() {
try {
// select name, age from demo
CommonUtil.printList(MAPPER.dynamicSql());
} finally {
ConnectUtil.release(SQLSESSION);
}
}
public void dynamicBind() {
try {
// select * from demo where name like ?
CommonUtil.printList(MAPPER.dynamicBind(new Demo(null, "小", null)));
} finally {
ConnectUtil.release(SQLSESSION);
}
}
DemoMapper.xml
<sql id="sql_property">name, age</sql>
<sql id="sql_select">select <include refid="sql_property"/> from demo</sql>
<select id="dynamicSql" resultType="demo">
<include refid="sql_select"></include>
</select>
<select id="dynamicBind" parameterType="demo" resultType="demo">
<!-- 2种绑定方式都可获取到name值-->
<bind name="pattern" value="'%' + demo.name + '%'"/>
<bind name="pattern2" value="'%' + demo.getName() + '%'"/>
select * from demo
where name like #{pattern3}
</select>
UT测试用例 - DynamicSqlTest.java
@Test
public void dynamicBind(){
DEMO.dynamicBind();
}
4.5 注解 - 增删改查
在Mapper接口绑定类上,通过@Select,@Insert,@Update,@Delete等注解进行完成增删改查,更详细的注解使用,访问官网。笔者这里编写的是基本的增删改查操作。
4.5.1 步骤流程
- 创建一个DemoAnnotationMapper.java
- 在该接口类里编写crud,并添加注释
- 测试用例
4.5.2 代码实现
DemoAnnotationMapper.java
/**
* @author zhanghp
* @date 2023/6/26 22:32
*/
public interface DemoAnnotationMapper {
/**
* 查询全部
*
* @return {@link com.zhanghp.dao.pojo.Demo}
*/
@Select("select * from demo")
List<Demo> queryAll();
/**
* 通过主键查询单条记录
*
* @return {@link com.zhanghp.dao.pojo.Demo}
*/
@Select("select * from demo where id = #{id}")
Demo queryById(@Param(value = "id") Integer id);
/**
* 通过map匹配对应的对象集合
*
* @return {@link com.zhanghp.dao.pojo.Demo}
*/
@Select("select * from demo where name = #{map.name} and age = #{map.age}")
List<Demo> queryByMap(@Param(value = "map") Map<String, Object> map);
/**
* 通过对象指定的信息查询所匹配的集合
*
* @return {@link com.zhanghp.dao.pojo.Demo}
*/
@Select("select * from demo where name = #{demo.name} and age = #{demo.age}")
List<Demo> queryByDemo(@Param(value = "demo") Demo demo);
/**
* 通过map插入
*
* @param map 信息
* @return 插入成功的行数
*/
@Insert("insert into demo values (#{map.id}, #{map.name}, #{map.age})")
int insertByMap(@Param(value = "map") Map<String, Object> map);
/**
* 通过对象插入
*
* @param demo 实体类
* @return 插入成功行数
*/
@Insert("insert into demo values (#{demo.id}, #{demo.name}, #{demo.age})")
int insertByDemo(@Param(value = "demo") Demo demo);
/**
* 通过指定的几个参数修改
*
* @param name 姓名
* @param age 年龄
* @param id 主键
* @return 修改成功的行数
*/
@Update("update demo set name = #{name}, age = #{age} where id = #{id}")
int updateByParams(@Param(value = "name") String name,
@Param(value = "age") Integer age,
@Param(value = "id") Integer id);
/**
* 通过主键删除对应的记录
*
* @param id 主键
* @return 删除成功行数
*/
@Delete("delete from demo where id = #{id}")
int deleteById(@Param(value = "id") Integer id);
}
UT测试用例 - DemoAnnotationTest.java
/**
* @author zhanghp
* @date 2023/6/26 11:21
*/
public class DemoTest {
final SqlSession SQLSESSION = ConnectUtil.getSqlSession();
final DemoAnnotationMapper mapper = SQLSESSION.getMapper(DemoAnnotationMapper.class);
@Test
public void queryAll() {
CommonUtil.printList(mapper.queryAll());
}
@Test
public void queryById() {
System.out.println(mapper.queryById(1));
}
@Test
public void queryByMap() {
Map<String, Object> map = new HashMap<>();
map.put("name", "小陈");
map.put("age", 18);
CommonUtil.printList(mapper.queryByMap(map));
}
@Test
public void queryByDemo() {
CommonUtil.printList(mapper.queryByDemo(new Demo(null, "小陈", 18)));
}
@Test
public void insertByMap(){
Map<String, Object> map = new HashMap<String, Object>(){{
put("id", 200);
put("name", "小白");
put("age", 10);
}};
System.out.println(mapper.insertByMap(map));
}
@Test
public void insertByDemo(){
System.out.println(mapper.insertByDemo(new Demo(300, "小黑", 10)));
}
@Test
public void updateByParams(){
System.out.println(mapper.updateByParams("小红", 20, 300));
}
@Test
public void deleteById(){
System.out.println(mapper.deleteById(300));
}
}
五. 遇见的异常
5.1 异常一:Type interface com.zhanghp.dao.mapper.DemoMapper is already known to the MapperRegistry.
问题原因:
这个问题就是接口无法Mapper注册,导致这个原因可能是在,我们MaBatis核心配置文件只的中的mapper和package扫包冲突导致的
解决方式:
mybatis-config.xml配置里,配置的
当指定使用的是接口绑定的方式,就不需要单独扫描xml了,改成扫描包路径
<mappers>
<!--
Exception:
Type interface com.zhanghp.dao.mapper.DemoMapper is already known to the MapperRegistry.
<!-- <mapper resource="com/zhanghp/dao/mapper/DemoMapper2.xml"/>-->
<!-- 扫描代理接口模式类包路径 -->
<package name="com.zhanghp.dao.mapper"></package>
</mappers>
5.2 异常二:Cannot define both nestedQueryId and nestedResultMapId in property dept
问题原因:
在标签里使用了seletct,指定调用DeptMapper.xml里的queryByDeptno方而在queryByDeptno也指定了resultMap,导致嵌套异常(即使queryByDeptno方法用的是resulttype也一样)
解决方式:
把association里的resultMap属性删除
下面2个文件为抛异常相关的文件,其中EmpMapper.xml是出错的文件
mappers/EmpMapper.xml
<resultMap id="oneToOne2" type="emp">
<id property="empno" column="EMPNO"/>
<result property="name" column="ENAME"/>
<result property="job" column="JOB"/>
<result property="mgr" column="MGR"/>
<result property="hiredate" column="HIREDATE"/>
<result property="sal" column="SAL"/>
<result property="comm" column="COMM"/>
<result property="deptno" column="DEPTNO"/>
<association property="dept" javaType="dept" column="DEPTNO" select="com.zhanghp.dao.mapper.DeptMapper.queryByDeptno" resultMap="deptResultMap"/>
</resultMap>
mappers/DeptMapper.xml
<select id="queryConstructor" resultMap="deptResultMap">
select *
from dept
</select>
5.3 异常三:Mapped Statements collection does not contain value for queryMultiToMulti
问题原因:
映射语句集合不包含指定的queryMultiToMulti
通过这句话,可以看出,使用的是xml配置的扫描。
说明没有扫描到这个方法id,换句话说,mybatis-config.xml没有配置扫描该方法所在类的路径
解决方式:
在mybatis-config.xml中添加扫描该方法的类
<mappers>
<!-- 扫描xml路径 -->
<mapper resource="mappers/DemoMapper.xml"/>
<mapper resource="mappers/EmpMapper.xml"/>
<mapper resource="mappers/DeptMapper.xml"/>
<mapper resource="mappers/ProjectMapper.xml"/>
</mappers>
⭐️ 源码地址:https://gitee.com/zhp1221/java/tree/master/lab_04_mybatis