- Mybatis
- 一、初识mybatis
- 1.1 概述
- 1.2 第一个mybatis程序
- 1.3 第一个mybatis程序的坑
- 二、mybatis的CRUD
- 2.1 增
- 2.2 删
- 2.3 改
- 2.4 查
- 2.6 传参方式:Map传参
- 2.7 两种方式模糊查询
- 2.8 注意事项
- 三、mybatis配置
- 3.1 properties(属性)
- 3.2 environments(环境配置)
- 3.2.1 transactionManager
- 3.2.2 dataSource
- 3.3 typeAliases(类型别名)
- 3.4 mappers(映射器)
- 四、生命周期和作用域
- SqlSessionFactoryBuider:
- SqlSessionFactory
- SqlSession
- 五、resultMap
- 六、日志
- log4j
- 七、分页
- 7.1 Limit分页
- 7.2 RowBounds分页
- 7.3 插件分页
- 八、使用注解开
- 8.1 注解使用方法以及示例
- 8.2 使用注解完成CRUD
- 九、Lombok
- 十、复杂的查询
- 10.1 多对一
- 查询嵌套
- 结果嵌套
- 10.2 一对多
- 结果嵌套
- 查询嵌套
- 十一、动态SQL
- 11.1 if
- 11.2 where
- 11.3 choose、when、otherwise
- 11.4 set
- 11.5 trim
- 11.6 foreach
- 11.7 SQL代码片段
- 十二、缓存
- 测试环境搭建
- 12.1 一级缓存
- 12.2 二级缓存
我的环境:
mysql:8.0.28
jdk:1.8
mybatis:3.5.9
junit
一、初识mybatis 1.1 概述1.2 第一个mybatis程序MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
总的来说,用mybatis就可以不用写繁琐的JDBC代码。
来写一个mybatis程序就明白了
-
准备数据库:
DROp DATABASE IF EXISTS `mybatis`; CREATE DATABASE `mybatis`; CREATE TABLE `user` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, `pwd` varchar(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 insert into `user`(`name`,`pwd`) values ('root','123456'),('admin','123456'); -
导入依赖
junit junit 4.13.2 test org.mybatis mybatis 3.5.9 mysql mysql-connector-java 8.0.28 -
编写实体类
package vip.yangsf.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 + ''' + '}'; } } -
编写持久层接口
package vip.yangsf.dao; import vip.yangsf.pojo.User; import java.util.List; public interface UserMapper { ListgetUserList(); } -
配置映射文件 UserMapper.xml
-
编写mybatis配置文件mybatis-config.xml
-
编写工具类
package vip.yangsf.First; 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 FirstUtil { // 官网扒下来的代码稍加改造 private static String resource =null; private static InputStream inputStream = null; private static SqlSessionFactory sqlSessionFactory = null; static { resource = "mybatis-config.xml"; try { // 加载配置文件,创建工厂 inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession() { // 利用工厂创建SqlSession,SqlSession可以执行sql语句 return sqlSessionFactory.openSession(); } } -
测试
package vip.yangsf.dao; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import vip.yangsf.First.FirstUtil; import vip.yangsf.pojo.User; import java.util.List; public class UserDaoTest { @Test public void test() { SqlSession session = FirstUtil.getSqlSession(); UserMapper mapper = session.getMapper(UserMapper.class); ListuserList = mapper.getUserList(); for (User user : userList) { System.out.println(user); } session.close(); } } 输出:
按部就班的来,一般情况下只会遇到一个问题:找不到映射文件。
我当时排错排了很久,以为是版本原因,排了很久过后发现我注册mapper时路径结尾多了一个空格。。。。。
当然还有一个maven的问题,因为maven的约定大于配置,所以我们写在dao包下的配置文件无法导出,maven只将resources目录下的资源导出。于是,我们得去配置pom.xml:
src/main/resources ***.xml true src/main/java ***.xml true
在pom添加这一段配置,就可以导出包中的资源文件。
二、mybatis的CRUD不管我们怎么花里胡哨,mybatis始终时要用来CRUD的。
CRUD和第一个mybatis程序流程一样,我们只需要给UserMapper接口添加方法,然后在映射文件UserMapper,xml中添加映射。
2.1 增-
增的方法:
package vip.yangsf.dao; import vip.yangsf.pojo.User; import java.util.List; public interface UserMapper { // 查询全部User ListgetUserList(); // 添加User int addUser(User user); } -
修改UserMapper.xml:
-
测试:
class UserDaoTest { @Test public void test() { SqlSession session = FirstUtil.getSqlSession(); UserMapper mapper = session.getMapper(UserMapper.class); mapper.addUser(new User(3,"yaf","123456")); // 提交事务,不然添加无效 session.commit(); // 释放资源 session.close(); } }
-
和增一样的道理,先在UserMapper接口中添加删除方法:
package vip.yangsf.dao; import vip.yangsf.pojo.User; import java.util.List; public interface UserMapper { // 查询全部User ListgetUserList(); // 添加User int addUser(User user); // 通过id删除User int deleteUser(int id); } -
修改UserMapper.xml:
-
测试:
class UserDaoTest { @Test public void test() { SqlSession session = FirstUtil.getSqlSession(); UserMapper mapper = session.getMapper(UserMapper.class); // 删除id为3的记录 mapper.deleteUser(3); // 提交事务,不然添加无效 session.commit(); // 释放资源 session.close(); } }
-
一样的套路,先添加修改方法:
package vip.yangsf.dao; import vip.yangsf.pojo.User; import java.util.List; public interface UserMapper { // 查询全部User ListgetUserList(); // 添加User int addUser(User user); // 通过id删除User int deleteUser(int id); // 修改User int updateUser(User user); } -
修改xml配置,添加映射:
-
测试:
class UserDaoTest { @Test public void test() { SqlSession session = FirstUtil.getSqlSession(); UserMapper mapper = session.getMapper(UserMapper.class); mapper.updateUser(new User(2, "ysf", "123456")); // 提交事务,不然添加无效 session.commit(); // 释放资源 session.close(); } }
查和增删改不一样的是,查不需要提交事务,因为没有修改表。
其余都是套路:
-
添加查询方法
package vip.yangsf.dao; import vip.yangsf.pojo.User; import java.util.List; public interface UserMapper { // 查询全部User ListgetUserList(); // 添加User int addUser(User user); // 通过id删除User int deleteUser(int id); // 修改User int updateUser(User user); // 通过id查询某条记录 User getUserById(int id); } -
添加映射
-
测试
class UserDaoTest { @Test public void test() { SqlSession session = FirstUtil.getSqlSession(); UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.getUserById(1); System.out.println(user); } }
假如说要插入一个User,我们刚刚是通过UserMapper.xml来映射实体类,比如:
insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd});
我们就需要传个User进去,这些变量名还得与User中的成员变量名,数据库中的字段名一样。
我们可以传Map进去,看代码就明白了:
-
UserMapper接口中添加方法:
package vip.yangsf.dao; import vip.yangsf.pojo.User; import java.util.List; public interface UserMapper { // 查询全部User ListgetUserList(); // 添加User int addUser(User user); // 通过id删除User int deleteUser(int id); // 修改User int updateUser(User user); // 通过id查询某条记录 User getUserById(int id); // Map传参 int addUser1(Map map); } -
配置文件中添加一个映射:
insert into mybatis.user (name, pwd) values (#{username}, #{password}); -
测试:
class MapTest { @Test public void test() { SqlSession sqlSession = FirstUtil.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Mapmap = new HashMap<>(); // key名要和配置文件中对应 map.put("username", "map"); map.put("password", "123456"); // 传map过去就行 mapper.addUser1(map); // 提交事务 sqlSession.commit(); // 释放资源 sqlSession.close(); } }
本质上都一样,一种是通过java传参时手动输入通配符,一种是把通配符写死了。
先添加两条记录待会儿查找:
-
接口中添加方法:
List
getUserList1(String value); List getUserList2(String value); -
添加映射语句:
select * from mybatis.user where name like #{value}; select * from mybatis.user where name like "%"#{value}"%"; -
测试
// 查询所有name中带李的记录 class LikeUserTest { @Test public void test() { SqlSession sqlSession = FirstUtil.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); System.out.println("方式一:"); Listlist = mapper.getUserList1("%李%"); list.forEach(System.out::println); System.out.println("方式二:"); List list1 = mapper.getUserList2("李"); list1.forEach(System.out::println); sqlSession.close(); } } 输出:
增删改一定要记得提交事务,不然改了白改。
三、mybatis配置在前面的学习中,我们学会了一点配置,比如:
接下里我们稍微深入一点,学习这里面的一些标签。
这段xml文档是从官网扒下来的,长这样子:
可以发现,里面有几个像是变量的东西,比如:driver、url这些。可以猜想,这些值是可以通过外界传进来的。
3.1 properties(属性)和properties有关系的是这几行代码。
可以这样:
还可以引入外部文件:
db.properties:
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 username=root pwd=123456
mybatis-config.xml:
3.2 environments(环境配置)
通过标签名environments结尾的s,就可以猜想:是不是可以有多个环境?
事实上也是肯定的。
我们可以在environments中添加多个environment,但是每个SqlSessionFactory只能对应一个environment,我们在选择environment时可以通过environments的default属性的值来选择对应的environment,或是通过创建SqlSessionFactory时填写build方法的参数来选择。
3.2.1 transactionManager事务管理器
在 MyBatis 中有两种类型的事务管理器(也就是 type=“[JDBC|MANAGED]”):
MANAGEND不太懂,一般只用JDBC,后面的学习了Spring就不需要配置事务管理器了。
3.2.2 dataSource数据源,默认是POOLED,还有UNPOOLED UNPOOLED不能使用池化技术,POOLED就像是数据库连接池,可以为它设置一些属性如poolMaximumActiveConnections – 在任意时间可存在的活动(正在使用)连接数量,默认值:10,等等一些配置数据库连接池的一些属性。
3.3 typeAliases(类型别名)可以为Java类型设置一个缩写名字,仅用于xml配置文件中,比如之前是这样写的:
select * from mybatis.user;
resultType后面的User全类名有点冗余,我们在mybatis-config.xml中加上typeAliases,给
vip.yangsf.pojo.User取个别名:
标签的顺序也有讲究:
然后,在映射文件中就可以用user(不区分大小写)来代替vip.yangsf.pojo.User:
select * from mybatis.user;
当实体类较多时,可以直接给定一个包路径,mybatis会自动扫描这个包下面的所有JavaBean并给他们取个别名,别名就是类名:
如果有特别要求,可以在类上面加上@Alias("")来取类名:
@Alias("user")
public class User {
……
}
mybatis为一些常见的 Java 类型内建的类型别名。它们都是不区分大小写的。可以去https://mybatis.net.cn/configuration.html#typeAliases查看。
3.4 mappers(映射器)我们配置mybatis是为了执行sql,让mybatis找到要映射的文件,有四种方式:
-
使用相对路径
-
使用url
-
使用对应的接口名
-
使用包名,会将包中所有mapper注册
注意事项:
映射文件尽量与映射器接口名一样,不然后两种无法识别!
四、生命周期和作用域错误的控制生命周期和作用域会导致并发问题。
SqlSessionFactoryBuider:这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactorySqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的应用逻辑代码
}
五、resultMap
现在有这么一张表:
CREATE TABLE `user_test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`password` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
insert into `user`(`name`,`password`) values ('root','123456'),('admin','123456');
有这么一个JavaBean:
package vip.yangsf.pojo;
public class User1 {
private int id;
private String name;
private String pwd;
public User1() {
}
public User1(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 + ''' +
'}';
}
}
然后我们查询所有的User1:
pwd查不到,是因为数据库中字段为password,JavaBean中是pwd,我们可以用结果集映射来解决这种数据库字段与JavaBean属性名不一致问题。
修改映射器文件:
select * from mybatis.user_test
然后就能查出来了。
六、日志要打印日志,就要在mybatis-config.xml中添加日志配置。
STDOUT_LOGGING 是标准日志输出,可以直接用。
logImpl还有很多种日志框架可以选择:
log4jLog4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
-
使用阿帕奇的log4j,首先需要把包导进来:
在pom.xml中:
log4j log4j 1.2.17 -
让mybatis使用log4j
-
配置log4j,在resources目录下添加log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码 log4j.rootLogger=DEBUG,console,file #控制台输出的相关设置 log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.Target = System.out log4j.appender.console.Threshold=DEBUG log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=[%c]-%m%n #文件输出的相关设置 log4j.appender.file = org.apache.log4j.RollingFileAppender log4j.appender.file.File=./log/xxx.log log4j.appender.file.MaxFileSize=10mb log4j.appender.file.Threshold=DEBUG log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n #日志输出级别 log4j.logger.org.mybatis=DEBUG log4j.logger.java.sql=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.ResultSet=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG -
我们还可以往日志里添加语句、设置日志格式等
package vip.yangsf.dao; import org.apache.log4j.Logger; import org.junit.Test; public class Log4jTest { static Logger logger = Logger.getLogger(Log4jTest.class); @Test public void test() { logger.info("info: 666"); logger.error("error: 666"); logger.debug("debug: 666"); } }
如果数据很多,就需要分页处理
7.1 Limit分页mybatis的流程我们已经熟悉了,大致就是三步
-
写接口
package vip.yangsf.dao; import vip.yangsf.pojo.User; import java.util.List; import java.util.Map; public interface LimitUser { ListgetLimitUser(Map map); } -
写sql
select * from mybatis.user limit #{startIndex}, #{pageSize}; -
测试
@Test public void test() { SqlSession sqlSession = FirstUtil.getSqlSession(); LimitUser mapper = sqlSession.getMapper(LimitUser.class); Mapmap = new HashMap<>(); map.put("startIndex", 3); map.put("pageSize", 2); List user = mapper.getLimitUser(map); user.forEach(System.out::println); }
java提供了一个类来帮助我们分页,就不用写分页的sql语句了(但查询还是要写)
-
写接口
package vip.yangsf.dao; import vip.yangsf.pojo.User; import java.util.List; import java.util.Map; public interface LimitUser { ListgetLimitUser(Map map); List getLimitUserByRowBounds(); } -
写sql
select * from mybatis.user limit #{startIndex}, #{pageSize}; select * from mybatis.user -
测试
@Test public void rowBounds() { SqlSession sqlSession = FirstUtil.getSqlSession(); Listusers = sqlSession.selectList("vip.yangsf.dao.LimitUser.getLimitUserByRowBounds", null, new RowBounds(3, 2)); users.forEach(System.out::println); } 效果和上面是一样的。
还可以用mybatis插件进行分页,比如PageHelper
八、使用注解开 8.1 注解使用方法以及示例步骤都是一样的,sql不用再写在xml里面
-
写接口
package vip.yangsf.mapper; import vip.yangsf.pojo.User; import java.util.List; public interface UserMapper { ListgetUserList(); } -
写sql
直接添加注解就完事:
package vip.yangsf.mapper; import org.apache.ibatis.annotations.Select; import vip.yangsf.pojo.User; import java.util.List; public interface UserMapper { @Select("select * from mybatis.user") ListgetUserList(); } -
测试
package vip.yangsf; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import vip.yangsf.mapper.UserMapper; import vip.yangsf.pojo.User; import vip.yangsf.utils.MyUtils; import java.util.List; public class Test01 { @Test public void test() { SqlSession sqlSession = MyUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); ListuserList = mapper.getUserList(); userList.forEach(System.out::println); } }
都是三步走!
-
写接口
package vip.yangsf.mapper; import org.apache.ibatis.annotations.*; import vip.yangsf.pojo.User; import java.util.List; public interface UserMapper { // 查询 ListgetUserList(); // 根据id查询 User getUserById(int id); // 增 int addUser(User user); // 增,利用id自增,只需要填写姓名和密码 int addUser1(String name, String password); // 改 int updateUser(User user); // 删 int deleteUser(int id); } -
写sql
package vip.yangsf.mapper; import org.apache.ibatis.annotations.*; import vip.yangsf.pojo.User; import java.util.List; public interface UserMapper { // 查询 @Select("select * from mybatis.user") ListgetUserList(); // 根据id查询 @Select("select * from mybatis.user where id = #{id}") User getUserById(int id); // 增 @Insert("insert into mybatis.user(id, name, pwd) values(#{id}, #{name}, #{pwd})") int addUser(User user); // 增,利用id自增,只需要填写姓名和密码 @Insert("insert into mybatis.user(name, pwd) values(#{name}, #{password})") int addUser1(@Param("name") String name, @Param("password") String password); // 改 @Update("update mybatis.user set name = #{name}, pwd = #{pwd} where id = #{id}") int updateUser(User user); // 删 @Delete("delete from mybatis.user where id = #{id}") int deleteUser(int id); } -
写测试
package vip.yangsf; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import vip.yangsf.mapper.UserMapper; import vip.yangsf.pojo.User; import vip.yangsf.utils.MyUtils; import java.util.List; public class Test01 { @Test public void test() { SqlSession sqlSession = MyUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 查询全部 ListuserList = mapper.getUserList(); userList.forEach(System.out::println); // 根据id查询 User user = mapper.getUserById(3); System.out.println(user); // 增 mapper.addUser(new User(6, "ysf", "123456")); // 增 mapper.addUser1("ysf", "123456"); // 改 mapper.updateUser(new User(6, "yangsf", "123456")); // 删 mapper.deleteUser(6); } }
Lombok项目是一个Java库,它会自动插入编辑器和构建工具中,Lombok提供了一组有用的注释,用来消除Java类中的大量样板代码。仅五个字符(@Data)就可以替换数百行代码从而产生干净,简洁且易于维护的Java类。
说起来复杂,但其实做起来很简单,导完包之后,就可以用它的各种注解来简化我们的代码了。
-
导包
org.projectlombok lombok 1.18.24 -
在实体类上加注解,例如@Data
package vip.yangsf.pojo; import lombok.Data; @Data public class User { private int id; private String name; private String pwd; } -
看结构
发现多了很多方法 get/set, toString…………方法都有了,但是有参构造不见了,可以添加@AllArgsConstructor,添加这个注解后无参构造不见了,我们再加上@NoArgsConstructor。非常完美。
当然,不只有@Data注解,还有很多注解
例如
十、复杂的查询@Setter :注解在类或字段,注解在类时为所有字段生成setter方法,注解在字段上时只为该字段生成setter方法。
@Getter :使用方法同上,区别在于生成的是getter方法。
@ToString :注解在类,添加toString方法。
@EqualsAndHashCode: 注解在类,生成hashCode和equals方法。
@NoArgsConstructor: 注解在类,生成无参的构造方法。
@RequiredArgsConstructor: 注解在类,为类中需要特殊处理的字段生成构造方法,比如final和被@NonNull注解的字段。
@AllArgsConstructor: 注解在类,生成包含类中所有字段的构造方法。
@Data: 注解在类,生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
@Slf4j: 注解在类,生成log变量,严格意义来说是常量。
----百度百科
真tm打脑壳。
10.1 多对一先来看看多对一的情况。
现在有两张表:
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, '秦老师');
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAr(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`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', '小王', '1');
我们需要的是,查询所有学生以及对应的老师信息。
sql这样写:
select s.id `学生id`, s.name `学生姓名`, t.id `老师id`, t.name `老师姓名` from student s inner join teacher t on s.tid = t.id
那再mybatis中应该怎么写呢?有两种方法,先看第一种
查询嵌套这就复杂起来了,这不是sql简单复制就可以实现的,还是按照步骤来。
-
先写实体类
Student的实体类,tid代表的是老师的信息,所以是Teacher类型,待会儿去老师表里查出来:
package vip.yangsf.pojo; import lombok.Data; @Data public class Student { private int id; private String name; private Teacher tid; }Teacher的实体类:
package vip.yangsf.pojo; import lombok.Data; @Data public class Teacher { private int id; private String name; } -
写接口:
package vip.yangsf.mapper; import vip.yangsf.pojo.Student; import java.util.List; public interface StudentMapper { ListgetList(); } -
写sql,这里就比较复杂了,我们先看一下答案:
select * from mybatis.student select * from mybatis.teacher where id = #{tid} 这里有几个难点:
-
查询Student返回的tid是int类型,但我们需要的是Teacher类型
解决:定义resultMap
-
查Teacher
解决:定义一个查询,在resultMap中,调用这个查询,将查到的teacher返回给tid。
-
-
测试
package vip.yangsf; import org.apache.ibatis.session.SqlSession; import vip.yangsf.mapper.StudentMapper; import vip.yangsf.pojo.Student; import vip.yangsf.utils.MyUtils; import java.util.List; public class test02 { public static void main(String[] args) { SqlSession sqlSession = MyUtils.getSqlSession(); StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); Listlist1 = studentMapper.getList(); list1.forEach(System.out::println); } } 结果:
再来看第二种,第二种的sql语句就简单得多
结果嵌套实体类和接口已经写好了,直接开始写sql:
select s.*, t.id as ttid, t.name as tname from mybatis.student s, mybatis.teacher t where s.tid = t.id;
不用嵌套sql,我们直接将查出来的字段名和java类的成员变量做映射。
测试还是一样的代码:
package vip.yangsf;
import org.apache.ibatis.session.SqlSession;
import vip.yangsf.mapper.StudentMapper;
import vip.yangsf.pojo.Student;
import vip.yangsf.utils.MyUtils;
import java.util.List;
public class test02 {
public static void main(String[] args) {
SqlSession sqlSession = MyUtils.getSqlSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
List list1 = studentMapper.getList();
list1.forEach(System.out::println);
}
}
结果:
10.2 一对多一个老师对应多个学生,同样的也有两种方法,步骤大同小异。
结果嵌套-
实体类
teacher:
import lombok.Data; import java.util.List; @Data public class Teacher { private int id; private String name; // 一对多 private Liststudents; } student:
import lombok.Data; @Data public class Student { private int id; private String name; private int tid; } -
写接口
TeacherMapper
import vip.yangsf.pojo.Teacher; import java.util.List; public interface TeacherMapper { ListgetList(); } -
写sql
TeacherMapper.xml:
select t.id as ttid, t.name as tname, s.* from mybatis.teacher t, mybatis.student s where tid = t.id 将查出来的字段和Java实体类中一一对应
由于 一个老师对应一个学生集合,所以将查出来的所有Student装在集合中,集合中的每一个对象类型用ofType设定,集合的结果集映射用collection标签,
将查出来的字段和Student类中一一对应,Student就装在了集合中。
Teacher的成员变量也和查出来的字段一一对应。
-
测试
import org.apache.ibatis.session.SqlSession; import vip.yangsf.mapper.TeacherMapper; import vip.yangsf.pojo.Teacher; import vip.yangsf.utils.MyUtils; import java.util.List; public class Test03 { public static void main(String[] args) { SqlSession sqlSession = MyUtils.getSqlSession(); TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); Listteachers = mapper.getList(); teachers.forEach(System.out::println); } } 结果:
接口和实体类不再写,看看sql怎么写:
select * from mybatis.teacher select * from mybatis.student where tid = #{id}
将老师查出来,然后把查出来的 老师的 id 用在查询对应的学生中,所以我们定义了一个结果集映射,做了个嵌套查询,因为id用于子查询了,所以要将id载映射给Teacher对象的id。
测试:
package vip.yangsf;
import org.apache.ibatis.session.SqlSession;
import vip.yangsf.mapper.TeacherMapper;
import vip.yangsf.pojo.Teacher;
import vip.yangsf.utils.MyUtils;
import java.util.List;
public class Test03 {
public static void main(String[] args) {
SqlSession sqlSession = MyUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
List teachers = mapper.getList();
teachers.forEach(System.out::println);
}
}
结果:
个人觉得结果嵌套的方式更好用,因为只需要关心查询的结果,将查询出来的数据与实体类做映射就可以了。
复杂的查询确实复杂,需要总结一下新学到的东西:
- association标签,多对一,做对象的结果映射
- collection标签,一对多,做集合的结果映射
- 对应Java中的类用javaType
- 对应集合中元素类型用ofType
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
说人话就是,根据条件选择拼接的语句,也就是在sql层面加了一些逻辑判断。
先搭个环境:
表:
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;
工具类:
获取SqlSession的工具类不必再多说,我们在这里建一个获取id的工具类(因为这次id不是主键自增)
使用UUID:
import java.util.UUID;
public class IDUtils {
public static String getID() {
// jian
return UUID.randomUUID().toString().replaceAll("-", "");
}
}
表有了,再插入几个数据:
活学活用,写个接口添加数据:
package vip.yangsf.mapper;
import org.apache.ibatis.annotations.Insert;
import vip.yangsf.entity.Blog;
import java.util.List;
public interface BlogMapper {
@Insert("insert into mybatis.blog(id, title, author, create_time, views) values (#{id}, #{title}, #{author}, #{createTime}, #{views})")
int addBlog(Blog blog);
}
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import vip.yangsf.entity.Blog;
import vip.yangsf.mapper.BlogMapper;
import vip.yangsf.utils.IDUtils;
import vip.yangsf.utils.MyUtils;
import java.util.Date;
public class Test01 {
@Test
public void test01() {
SqlSession sqlSession = MyUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
for (int i = 1; i <= 5; i++) {
mapper.addBlog(new Blog(IDUtils.getID(), "第" + i + "篇", (int)(Math.random()*100) + "", new Date(), 100));
}
sqlSession.commit();
sqlSession.close();
}
}
环境搭好后,表长这样
11.1 if假如现在我们需要实现:根据title查找blog,如果输入有author,那么就根据title和author查找。
如果用JDBC,就需要判断和拼接sql,头大。
使用mybatis,就轻松的多:
写接口:
import vip.yangsf.entity.Blog;
import java.util.List;
import java.util.Map;
public interface BlogMapper {
List searchBlogs(Map map);
}
传入一个map,根据参数来选择查询方式。
写sql:
select * from mybatis.blog where title like "%"#{title}"%" and author like "%"#{author}"%"
如果传入的title不为空,那么就拼接这条sql语句,如果author不为空,那么拼接sql语句。
测试:
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import vip.yangsf.mapper.BlogMapper;
import vip.yangsf.utils.MyUtils;
import java.util.HashMap;
import java.util.Map;
public class Test01 {
@Test
public void test01() {
SqlSession sqlSession = MyUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map
传入一个map。
11.2 where上面程序有一个问题:如果不传入title,只传入author,那么sql就会错误。
如和选择一个语句执行呢?
当然,可以这样
select * from mybatis.blog where 1=1 and title like "%"#{title}"%" and author like "%"#{author}"%"
可是,若是要拼接or语句就会出问题,这时候,就可以用到where标签
select * from mybatis.blog and title like "%"#{title}"%" and author like "%"#{author}"%"
测试:
import java.util.HashMap;
import java.util.Map;
public class Test01 {
@Test
public void test01() {
SqlSession sqlSession = MyUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map
当然也是成功的。
执行过程:
where标签下的,会选择性拼接,拼接很灵性,如果第一个条件开头是and或者or,那么where就会将and或者or替换掉,后面满足条件就拼接就完事了。
11.3 choose、when、otherwise用了where标签过后,sql已经很动态了。
现在还有一种情况:有时候只需要满足一个条件,如果一个条件都不满足,那么就得返回全部数据,这不合理。
于是,choose、when、otherwise来了。类似于switch-case
select * from mybatis.blog title like "%"#{title}"%" author like "%"#{author}"%" title = 1
只能满足一个条件,所以不用and。如果都不满足则条件为title=1。
11.4 set动态sql的查找我们倒是有些了解了,那修改呢?
-
写接口方法:
int updateBlogs(Map map);
-
写sql
这里就有一些讲究了
update mybatis.blog title = #{title}, author = #{author}, id = #{id} 会根据条件选择拼接哪条语句,并且会删掉最后一个逗号。
-
测试:
import org.apache.ibatis.session.SqlSession; import vip.yangsf.mapper.BlogMapper; import vip.yangsf.utils.MyUtils; import java.util.HashMap; import java.util.Map; public class Test02 { public static void main(String[] args) { SqlSession sqlSession = MyUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); Map map = new HashMap(); map.put("title", "第8篇"); map.put("author", "张5"); map.put("id", "a928fd4d8b7a4f689d92e383a06e140e"); mapper.updateBlogs(map); sqlSession.commit(); sqlSession.close(); } }当然效果也是杠杠的。
set 和 where都可以用trim定义
set就是
...
where就是
...
还可以自定义功能:
...
这样写意给sql拼接where,将最前面的and 或者 or去掉 ,将最后的逗号去掉,然后再拼接上666
了解即可。
11.6 foreach先看官方示例:
SELECT * FROM POST P WHERe ID in #{item}
先拼接open字符串,然后将list集合里面的所有item拼接进来,通过分隔符逗号分隔,然后结尾为close字符串也就是扩号。index就是每个item的下标。
实例:
写个接口方法,准备查询id 为 1~3 的Blog。记得先去把表中找三个记录,把它们的uuid改成1,2,3中的一个。
ListgetSomeBlogs(Map map);
不知道传什么,就传map,万能。
sql:
select * from mybatis.blog id = #{id}
解释一下:
查询不必多说,foreach还是在拼接sql,将传进来的map中key为ids的集合遍历,将每一个元素叫做id,开始为and ( 中间的每一个条件用 or 分隔,结尾是 )。
拼出来大概就长这样
select * from mybatis.blog WHERe ( id = ? or id = ? or id = ? )
测试一下:
@Test
public void test() {
SqlSession sqlSession = MyUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
List ids = new ArrayList<>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids", ids);
System.out.println(mapper.getSomeBlogs(map));
sqlSession.close();
}
11.7 SQL代码片段
有点封装的感觉,可以写一段sql语句作为公用sql,需要时就调用。
例:
title = #{title}, author = #{author}, update mybatis.blog id = #{id}
定义时用sql标签定义,使用时用include标签引用,了解即可。
十二、缓存缓存就是临时存储,可以提高数据交互速度。
测试环境搭建MyBatis 在执行一次SQL查询或者SQL更新之后,这条SQL语句并不会消失,而是被MyBatis 缓存起来,当再次执行相同SQL语句的时候,就会直接从缓存中进行提取,而不是再次执行SQL命令。. MyBatis中的缓存分为一级缓存和二级缓存
一张user表,长这样
随便添加点数据进去。
然后接口和sql:
import org.apache.ibatis.annotations.Param;
import vip.yangsf.entity.User;
import java.util.List;
import java.util.Map;
public interface UserMapper1 {
List getAll();
User queryUserById(@Param("id") int id);
int addUser(Map map);
}
sql:
12.1 一级缓存select * from mybatis.user select * from mybatis.user id = #{id} insert into mybatis.user(name, pwd) VALUES (#{name}, #{pwd})
一级缓存又被称为 SqlSession 级别的缓存。
同一个 SqlSession 对象, 在参数和 SQL 完全一样的情况先, 只执行一次 SQL 语句(如果缓存没有过期)。
一级缓存是默认开启的且关不掉。
看代码更容易理解:
@Test
public void test() {
SqlSession sqlSession = MyUtil.getSqlSession();
UserMapper1 mapper = sqlSession.getMapper(UserMapper1.class);
User user1 = mapper.queryUserById(1);
System.out.println(user1);
System.out.println("==================================");
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession.close();
}
查看日志会发现:
只有一次会话,而且user1和user2是同一个东西(输出了true)。
除了查询以外的其他操作会刷新缓存:
@Test
public void test() {
SqlSession sqlSession = MyUtil.getSqlSession();
UserMapper1 mapper = sqlSession.getMapper(UserMapper1.class);
User user1 = mapper.queryUserById(1);
System.out.println(user1);
System.out.println("==================================");
Map map = new HashMap();
map.put("name", "qqq");
map.put("pwd", "877602782");
mapper.addUser(map);
System.out.println("==================================");
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession.close();
}
发生了三次会话并且 user1和user2不是同一个东西(输出了false):
也可以手动清除缓存:
@Test
public void test() {
SqlSession sqlSession = MyUtil.getSqlSession();
UserMapper1 mapper = sqlSession.getMapper(UserMapper1.class);
User user1 = mapper.queryUserById(1);
System.out.println(user1);
System.out.println("==================================");
// 清除缓存
sqlSession.clearCache();
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession.close();
}
两次会话,并且两个user不是同一个(输出了false):
12.2 二级缓存二级缓存存在于 SqlSessionFactory 生命周期中。二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。UserMapper有一个二级缓存区域(按namespace分),其它mapper也有自己的二级缓存区域(按namespace分)。每一个namespace的mapper都有一个二级缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。
看代码更好理解:
@Test
public void test02() {
SqlSession sqlSession = MyUtil.getSqlSession();
UserMapper1 mapper = sqlSession.getMapper(UserMapper1.class);
SqlSession sqlSession2 = MyUtil.getSqlSession();
UserMapper1 mapper2 = sqlSession2.getMapper(UserMapper1.class);
User user1 = mapper.queryUserById(1);
System.out.println(user1);
sqlSession.close();
System.out.println("==================================");
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession2.close();
}
}
如果不开二级缓存,结果就是这样:
开启二级缓存:
- mybatis-config.xml里面添加配置:
默认就是开启的。
-
在要使用二级缓存的Mapper中开启:
-
测试:
同样的代码,看结果:
第二次查询直接就在缓存中取了,并且两个user相同(输出true)
当然 也可以不用设置参数:
上面的第二步变为:
并且要给实体类实现序列化接口
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private int id;
private String name;
private String pwd;
}
测试:
第二次查询是从缓存中查,但是输出为false
只需要将缓存设置为只读:
完美。



