目录
可以参考视频:【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂_哔哩哔哩_bilibili
github 练习: https://github.com/abbywei123/mybatis-study-2021-10
MyBatis-9.28
环境:
-
JDK1.8
-
Mysql 5.7
-
maven3.6.1
-
IDEA
回顾
-
JDBC
-
Mysql
-
Java
-
Maven
-
Junit
1.简介
1.1 什么是MyBatis?
-
MyBatis 是一款优秀的持久层框架,
-
它支持自定义 SQL、存储过程以及高级映射。
-
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
-
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
如何获得MyBatis?
-
maven仓库
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
1.2 持久化
数据持久化
-
持久化就是将程序额数据在持久状态时状态转化的过程
-
内存:断电即失
-
数据库(jdbc),io文件持久化
为什么需要持久化?
内存贵
1.3 持久层
Dao层 Service层 controller层。。。
-
完成持久化工作的代码块
-
层是界限明显的
1.4 为什么需要Mabitis?
-
传统的jdbc代码太复杂了,框架 自动化
-
方便
有点:
-
简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
-
灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
-
解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
-
提供映射标签,支持对象与数据库的orm字段关系映射
-
提供对象关系映射标签,支持对象关系组建维护
-
提供xml标签,支持编写动态sql。
2 第一个MyBatis程序
思路:搭建环境->导入MaBatis-》编写代码-》测试
2.1 搭建环境
-
创建一个amven项目
-
删除src目录
-
导入maven依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>Mybatis-Study</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>mybatis-01</artifactId> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <build> <!--这样也可以把所有的xml文件,打包到相应位置。 --> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> <include>**/*.tld</include> </includes> <filtering>true</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> <include>**/*.tld</include> </includes> <filtering>true</filtering> </resource> </resources> </build> </project>
2.2 创建项目
-
编写核心配置文件 在 src/main.esource 的 mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="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?useSSL=false&usUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- <mapper class="com.yw.dao.UserMapper"/>-->
<mapper resource="com/yw/dao/UserMapper.xml"/>
</mappers>
</configuration>
-
编写mybatis 工具类
package com.yw.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;
//SqlSessionFactory 获取 SqlSession
public class MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
String resorce="mybatis-config.xml";
try {
//获取 SqlSessionFactory
InputStream inputStream= Resources.getResourceAsStream(resorce);
sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
2.3 编写代码
-
实体类
public class User { private int id; private String name; private String pwd; public User() { }
-
Dao接口
public interface UserDao { public List<User> getUserList(); }
-
接口实现类
-
编写 基于 xml sql映射语句
<?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.yw.dao"> <select id="getUserList" resultType="com.yw.pojo.User"> select * from mybatis.user </select> </mapper>
-
-
测试
@Test
public void test2(){
SqlSession sqlSession= MyBatisUtil.getSqlSession();
try{
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
System.out.println(userMapper.getUserList());
}catch (Exception e){
e.printStackTrace();
}finally {
sqlSession.close();
}
}
-
结果
注意点!
报错信息可能:
(1)
org.apache.ibatis.binding.BindingException: Type interface com.yw.dao.UserDao is not known to the MapperRegistry.
<mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers>
是因为缺少次配置信息
每一个mapper.xml都需要在Mybatis核心配置文件中注册
Could not find resource com/yw/dao/UserMapper.xml
(2)
Could not find resource com/yw/dao/UserMapper.xml
解决方法:需要在pom.xml中 开启资源过滤
<build>
<!--这样也可以把所有的xml文件,打包到相应位置。 -->
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.tld</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.tld</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
又官方文档知道,还有一种方法来执行sql语句
try (SqlSession session = sqlSessionFactory.openSession()) {
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}
2.3 涉及的知识点补充
从 SqlSessionFactory 中获取 SqlSession
SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。例如:
try (SqlSession session = sqlSessionFactory.openSession()) {
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}
但现在有了一种更简洁的方式——使用和指定语句的参数和返回值相匹配的接口(比如 BlogMapper.class),现在你的代码不仅更清晰,更加类型安全,还不用担心可能出错的字符串字面值以及强制类型转换。
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
}
3.实现增删改 CRUD
3.1 namepace
namepace 中的包名要和 dao/mapper 接口的包名一致
3.2 select
查询语句
<select id="getUserList" resultType="com.yw.pojo.User">
select * from mybatis.user
</select>
-
id:就是对应 的namespace 中的对应方法名
-
resultType :sql 语句执行的返回值
-
parameterType:传入参数类型
例如:
根据 id 查询 用户
<select id="getUserById" resultType="com.yw.pojo.User" parameterType="int">
select * from mybatis.user where id=#{id}
</select>
#{id} 需要跟namespace签名中的 参数名称一致
3.3 insert
<insert id="addUser" parameterType="com.yw.pojo.User">
insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
注意,增删改需要增加事务提交 sqlSession.commit();
@Test
public void test3(){
SqlSession sqlSession= MyBatisUtil.getSqlSession();
try{
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
System.out.println(userMapper.addUser(new User(5,"小学","233444")));
}catch (Exception e){
e.printStackTrace();
}finally {
//提交事务
sqlSession.commit();
sqlSession.close();
}
}
3.4 update
<update id="updateUSer" parameterType="com.yw.pojo.User">
update mybatis.user set name=#{name} where id=#{id}
</update>
3.5 delte
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id=#{id}
</delete>
4 map 和 模糊查询
4.1使用map
-
如果实体类字段过多,map操作起来较为灵活些
-
传参含多个参数而也可以使用map 或 注解
<insert id="addUser2" parameterType="map">
insert into mybatis.user (id,name,pwd) values (#{userid},#{username},#{userpwd})
</insert>
4.2 模糊查询 like
select * from user where name like '%小%'
方法一:传参 写死
string value="%小%"
select * from user where name like #{value}
方法二:
select * from user where name like “%” #{value} “%”
5.xml 配置解析
5.1 properties 属性
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。例如:
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&usUnicode=true&characterEncoding=UTF-8
username=root
password=root
<properties resource="db.properties"/>
设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。比如:
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
这个例子中的 username 和 password 将会由 properties 元素中设置的相应值来替换。 driver 和 url 属性将会由 config.properties 文件中对应的值来替换。这样就为配置提供了诸多灵活选择。
5.2 环境配置(environments)
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单:
-
每个数据库对应一个 SqlSessionFactory 实例
为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
如果忽略了环境参数,那么将会加载默认环境,如下所示:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);
environments 元素定义了如何配置环境。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<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>
注意一些关键点:
-
默认使用的环境 ID(比如:default="development")。
-
每个 environment 元素定义的环境 ID(比如:id="development")。
-
事务管理器的配置(比如:type="JDBC")。
-
数据源的配置(比如:type="POOLED")。
默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。
事务管理器(transactionManager)
在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
数据源(dataSource)
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
-
大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):
5.3 类型别名(typeAliases)
-
类型别名可为 Java 类型设置一个缩写名字。 <typeAliases> <typeAlias type="com.yw.pojo.User" alias="User"/> </typeAliases>
-
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean
即,扫描实体类的包,他的默认别名就为这个类的类名,首字母小写
<typeAliases>
<package name="com.yw.pojo"/>
</typeAliases>
扫描包的情况下,可以在实体类上增加注解 @Alias("user") 取别名
见的 Java 类型内建的类型别名
5.4 设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true | false | false |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
5.5 其他配置
plugins 插件
-
mybatis-genertator-core
-
mybatis-plus
-
通用maper
5.6 映射器(mappers)
你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:///
形式的 URL),或类名和包名等。例如:
-
使用相对于类路径的资源引用【推荐使用】
<mappers>
<mapper resource="com/yw/dao/UserMapper.xml"/>
</mappers>
-
使用映射器接口实现类的完全限定类名
<mappers>
<mapper class="com.yw.dao.UserMapper"/>
</mappers>
注意:
-
接口和mapper配置文件必须同名
-
接口和mapper配置文件必须在同一个包下
3.将包内的映射器接口实现全部注册为映射器
<mappers>
<package class="com.yw.dao"/>
</mappers>
注意:
-
接口和mapper配置文件必须同名
-
接口和mapper配置文件必须在同一个包下
6 生命周期和作用域
-
SqlSessionFactoryBuilder
-
一旦创建了 SqlSessionFactory,就不再需要它了
-
局部变量
-
-
SqlSessionFactory
-
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
-
因此 SqlSessionFactory 的最佳作用域是应用作用域。
-
最简单的就是使用单例模式或者静态单例模式。
-
-
SqlSession
-
连接到连接池的一个请求
-
每个线程都应该有它自己的 SqlSession 实例
-
SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
-
每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要
-
这里的每一个mapper 就代表一个具体的业务
7.ResultMap 结果集映射
7.1 问题
解决属性名和字段名不一致的问题
实体类为
public class User {
private int id;
private String name;
private String password;
getId查询结果为
User{id=1, name='小白', pwd='null'}
解决方法:
-
起别名
select id,name,pwd as password from mybatis.user where id=#{id}
7.2 解决 resultMap
结果集映射
id name pwd id name pasword
<resultMap id="UserMap" type="User">
<!-- <result column="id" property="id"/>-->
<!-- <result column="name" property="name"/>-->
<result column="pwd" property="password"/>
</resultMap>
<select id="getUserById" resultMap="UserMap" parameterType="int">
select * from mybatis.user where id=#{id}
</select>
标签 resultMap column 数据库中的字段 property 实体类中的属性
8.日志
Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:
-
SLF4J
-
Apache Commons Logging
-
Log4j 2
-
Log4j
-
JDK logging
MyBatis 内置日志工厂会基于运行时检测信息选择日志委托实现。它会(按上面罗列的顺序)使用第一个查找到的实现。当没有找到这些实现时,将会禁用日志功能。
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
---|---|---|---|
8.1 STDOUT_LOGGING 标准日志输出
mybatis.xml
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
测试结果
8.2 使用 LOG4J
社么是log4j?
-
我们也可以控制每一条日志的输出格式;
-
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
-
可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
1.导入包
pom.xml
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2.log4j.properties
### set log levels ###
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.DailyRollingFileAppender
log4j.appender.file.File = ./log/log.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
3,配置log4j为日志的实现
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
4.简单使用
-
在要是使用Log4j的类中,导入包import org.apache.log4j.Logger;
-
日志对象,参数为当前类的class
static Logger logger=Logger.getLogger(UserDaoTest.class);
测试
@Test
public void testlog4j(){
logger.info("info===========");
logger.debug("debug==========");
logger.error("erroe===========");
}
会输出到文件中 /log/log.log
[INFO][21-10-16][com.yw.dao.UserDaoTest]info===========
[DEBUG][21-10-16][com.yw.dao.UserDaoTest]debug==========
[ERROR][21-10-16][com.yw.dao.UserDaoTest]erroe===========
9 分页
9.1 使用limit 分页
sql :
select * from use limt 0,2 [startIndex,pagesize]
使用mybatis 实现分类
接口
//分页
List<User> getUserByLimit(Map<String,Integer> map);
xml
<select id="getUserByLimit" resultType="User" parameterType="map">
select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
测试
@Test
public void getUserByLimit(){
SqlSession sqlSession= MyBatisUtil.getSqlSession();
try{
HashMap<String,Integer> map=new HashMap<>();
map.put("startIndex",1);
map.put("pageSize",2);
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
List<User> users=userMapper.getUserByLimit(map);
for (User user : users) {
System.out.println(user);
}
}catch (Exception e){
e.printStackTrace();
}finally {
sqlSession.close();
}
}
测试结果
User{id=2, name='张三', pwd='1235456'} User{id=3, name='李四', pwd='125456'}
9.2 RowBounds 分页
不使用SqL实现分页
接口
//RowBounds
List<User> getUserByRowBounds();
mappwe.xml
<select id="getUserByRowBounds" resultMap="UserMap" >
select * from mybatis.user
</select>
测试
@Test
public void getUserByRowBounds(){
SqlSession sqlSession= MyBatisUtil.getSqlSession();
try{
//rowBounds 实现
RowBounds rowBounds= new RowBounds(1,2);
List<User> users=sqlSession.selectList("com.yw.dao.UserMapper.getUserByRowBounds",null,rowBounds);
for (User user : users) {
System.out.println(user);
}
}catch (Exception e){
e.printStackTrace();
}finally {
sqlSession.close();
}
}
9.3 分页插件
MyBatis 分页插件 PageHelper
文档参考 如何使用分页插件
10使用注解开发
10.1 面向接口编程
根本原因:解耦
案例
10.2 select
接口
@Select("select * from user")
public List<User> getUsers();
@Select("select * from user where id=#{id}")
User getUserById(@Param("id")int id);
mybatis-config.xml
<mappers>
<mapper class="com.yw.dao.UserMapper"/>
</mappers>
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
11 MyBatis 详细的执行过程
1.Resours 获取加载全局配置文件
2.实例化SqlSessionFactoryBuiler 构造器
3.解析配置文件 XMLConfigbuilder
4.Configuration 所有的配置信息
5.SqlSessionFactory 实例化
6.tansaction 事务管理
7.创建 executor 执行器
8.创建 SqlSession
9.实现 CRUD
12 Lombok
Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
使用步骤:
1.导入依赖
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
有关注解
@Getter and @Setter @FieldNameConstants @ToString @EqualsAndHashCode @AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog @Data @Builder @SuperBuilder @Singular @Delegate @Value @Accessors @Wither @With @SneakyThrows @val @var experimental @var @UtilityClass Lombok config system
实体类
public class User {
private int id;
private String name;
private String password;
}
加 @Data 注解后
@Data
public class User {
private int id;
private String name;
private String password;
}
@Data : 无参构造,get ,set ,tostring ,hashcode ,equals 自动生成
有参无参注解
@AllArgsConstructor
@NoArgsConstructor
13 复杂查询
构建环境
数据库 关系
use mybatis;
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`),
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, '小王', 1);
实体类
@Data
public class Student {
private int id;
private String name;
private Teacher teacher;
}
@Data
public class Teacher {
private int id;
private String name;
}
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yw.dao.StudentMapper">
</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">
<mapper namespace="com.yw.dao.TeacherMapper">
</mapper>
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="db.properties"/>
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
<typeAliases>
<package name="com.yw.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>
<mapper class="com.yw.dao.TeacherMapper"/>
<mapper class="com.yw.dao.StudentMapper"/>
</mappers>
</configuration>
13.1 多对一处理
回顾mysql 多对一查询方式
1.子查询 2.联表查询
13.1.1 按照查询嵌套查询
StudentMapper.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.yw.dao.StudentMapper">
<select id="getStudent" resultMap="StudentTeacher">
select * from mybatis.student
</select>
<resultMap id="StudentTeacher" type="Student">
<result column="id" property="id"/>
<result column="name" property="name"/>
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from mybatis.teacher where id=#{id}
</select>
</mapper>
测试
[com.yw.dao.StudentMapper.getStudent]-==> Preparing: select * from mybatis.student
[com.yw.dao.StudentMapper.getStudent]-==> Parameters:
[com.yw.dao.StudentMapper.getTeacher]-====> Preparing: select * from mybatis.teacher where id=?
[com.yw.dao.StudentMapper.getTeacher]-====> Parameters: 1(Integer)
[com.yw.dao.StudentMapper.getTeacher]-<==== Total: 1
[com.yw.dao.StudentMapper.getStudent]-<== Total: 5
Student(id=1, name=小明, teacher=Teacher(id=1, name=秦老师))
Student(id=2, name=小红, teacher=Teacher(id=1, name=秦老师))
Student(id=3, name=小张, teacher=Teacher(id=1, name=秦老师))
Student(id=4, name=小李, teacher=Teacher(id=1, name=秦老师))
Student(id=5, name=小王, teacher=Teacher(id=1, name=秦老师))
先查询所有学生
再根据id查询老师 子查询
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
关键点
association 标签 复杂对象时使用
13.1.2 按照结果嵌套查询
StudentMapper.xml
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname,t.name tname
from mybatis.student s,mybatis.teacher t
where s.tid=t.id
</select>
<resultMap id="StudentTeacher2" type="Student">
<result column="sid" property="id"/>
<result column="sname" property="name"/>
<association property="teacher" javaType="Teacher" >
<result property="name" column="tname"/>
</association>
</resultMap>
13.2 一对多
比如,一个老师对应多个学生
实体类
Student
@Data
public class Student {
private int id;
private String name;
private int tid;
}
Teacher
@Data
public class Teacher {
private int id;
private String name;
//一个老师拥有多个学生
private List<Student> students;
}
方式一:
TeacherMapper.xml
<select id="getTeacherAll" resultMap="TeacherStudent">
select s.id sid,s.name sname,t.name tname,tid
from mybatis.teacher t,mybatis.student s
where s.tid=t.id
and tid=#{tid}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="id" column="sid" />
<result property="name" column="sname" />
<result property="tid" column="tid" />
</collection>
</resultMap>
测试结果
Teacher(id=1, name=秦老师, students=[Student(id=1, name=小明, tid=1), Student(id=2, name=小红, tid=1), Student(id=3, name=小张, tid=1), Student(id=4, name=小李, tid=1), Student(id=5, name=小王, tid=1)])
方式二:子查询
TeacherMapper.xml
<select id="getTeacherAll2" resultMap="TeacherStudent2">
select * from mybatis.teacher where id=#{tid}
</select>
<resultMap id="TeacherStudent2" 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=#{tid}
</select>
13.3 小结
-
关联- association
-
集合- collection
-
javaType & ofType
-
javaType 用来指定实体类中属性的类型
-
ofType 用来指定映射到list 或者集合中的pojo类型,泛型中的约束类型
-
13.3 动态sql 查询
什么是动态Sql:动态Sql就是根据不同的条件生成不同的sql语句
-
if
-
choose (when, otherwise)
-
trim (where, set)
-
foreach
搭建环境
数据表创建
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
实体类
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;
}
当数据字段名和类属性名不一致 create_time - createTime
在核心配置文件中
//是否开启驼峰命名
<setting name="mapUnderscoreToCamelCase" value="true"/>
13.3.1 if
BLogMapper
public interface BLogMapper {
int addBook(BLog bLog);
List<BLog> queryBlogIF(Map map);
}
BLogMapper.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.yw.dao.BLogMapper">
<select id="queryBlogIF" 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>
<insert id="addBook" parameterType="blog">
insert into mybatis.blog (id, title, author, create_time, views)
values (#{id}, #{title}, #{author}, #{createTime}, #{views})
</insert>
</mapper>
test
@Test
public void queryBlogIF(){
SqlSession session= MyBatisUtil.getSqlSession();
BLogMapper mapper=session.getMapper(BLogMapper.class);
HashMap map=new HashMap();
map.put("title","mybatis");
List<BLog> bLogs=mapper.queryBlogIF(map);
for (BLog bLog : bLogs) {
System.out.println(bLog);
}
}
结果
[com.yw.dao.BLogMapper.queryBlogIF]-==> Preparing: select * from mybatis.blog where 1=1 and title=?
[com.yw.dao.BLogMapper.queryBlogIF]-==> Parameters: mybatis(String)
[com.yw.dao.BLogMapper.queryBlogIF]-<== Total: 1
BLog(id=a48ad51727b24ab6b83d775bc11aed30, title=mybatis, author=哗哗哗, createTime=Sun Oct 17 12:52:43 CST 2021, views=99999)
Process finished with exit code 0
13.3.2 trim (where, set)
where
<where> 解决 上述问题
BLogMapper.xml
<select id="queryBlogChoose" 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>
set
<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>
13.3.3 choose、when、otherwise
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
<select id="queryBlogChoose" 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>
13.3.4 sql 片段
公共的sql 语句
对比
原
BLogMapper.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.yw.dao.BLogMapper">
<select id="queryBlogIF" 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>
<insert id="addBook" parameterType="blog">
insert into mybatis.blog (id, title, author, create_time, views)
values (#{id}, #{title}, #{author}, #{createTime}, #{views})
</insert>
</mapper>
改进
<sql id="if-title-author">
<if test="title!=null">
and title=#{title}
</if>
<if test="author!=null">
and author=#{author}
</if>
</sql>
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
注意事项:
-
最好给予的单表来定义sql 片段
-
不要存在where 标签
13.3.5 foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!
提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
例如
select * from user where 1=1 and (id =1 or id=2 or id=3)
可以用 foreach 表达
<select id="queryBlogforeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
测试
@Test
public void queryBlogforeach(){
SqlSession session= MyBatisUtil.getSqlSession();
BLogMapper mapper=session.getMapper(BLogMapper.class);
HashMap map=new HashMap();
ArrayList<Integer> ids=new ArrayList<>();
ids.add(1);
ids.add(2);
map.put("ids",ids);
List<BLog> bLogs=mapper.queryBlogforeach(map);
for (BLog bLog : bLogs) {
System.out.println(bLog);
}
}
14 缓存
14.1 简介
-
什么是缓存?
-
存在内存中的临时数据
-
将用户经常查询的地方放在缓存中(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
-
2.为什么使用缓存?
-
减少和数据的交互次数,减少系统开销,提高系统效率
3.什么样的数据使用缓存?
-
经常查询并且不经常改变的的数据
14.2 MyBatis 缓存
-
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 缓存可以极大的提升查询效率。
-
MyBatis 系统中默认定义了两级缓:一级缓存和二级缓存
-
默认情况下,只有一级缓存开启(SqlSession 级别的缓存,也称本地缓存)
-
二级缓存需要手动开启和配置,他是基于namepace 级别的缓存
-
为了提高扩展性,MyBatis 定义了缓存接口Cache 。 我们可以通过实现Cache接口来自定义二级缓存
-
14.3 一级缓存
-
一级缓存也叫本地缓存:
-
与数据库同一次会话期间查询到到二点数据会放在本地缓存中
-
以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
-
测试
-
开启日志
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
2.测试在一个sqlsession 查询两次相同记录
3.查看日志输出
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 1515638188.
[com.yw.dao.UserMapper.queryUserById]-==> Preparing: select * from mybatis.user where id=1
[com.yw.dao.UserMapper.queryUserById]-==> Parameters:
[com.yw.dao.UserMapper.queryUserById]-<== Total: 1
User(id=1, name=小白, pwd=123456)
==========================
User(id=1, name=小白, pwd=123456)
true
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5a56cdac]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 1515638188 to pool.
基本上就是这样。这个简单语句的效果如下:
-
映射语句文件中的所有 select 语句的结果将会被缓存。
-
映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
-
缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
-
缓存不会定时进行刷新(也就是说,没有刷新间隔)。
-
缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
-
缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
缓存失效的情况:
-
查询不同的东西
-
增删改操作,可能会改变原来的数据,所以必定会属性缓存
-
查询不同的Mapper.xml
-
手动清理缓存
-
sqlsession.clearCache()
-
小结:一级缓存是默认开启的,只在一次sqlsession 中有效,也就是拿到数据库到关闭数据库之间。++
14.4 二级缓存
-
二级缓存也叫全局缓存
-
基于namespace 级别的缓存,一个名词空间,对应一个二级缓存
-
工作机制
-
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
-
如果当前会话关闭了,这个会话对应的一级缓存就没了,但是我们想要的额是,会话关闭了,一级缓存中的数据保存到二级缓存中
-
新的会话查询信息,就可以从耳机缓存中获取数据
-
不同的mapper值查出的数据会放在自己对应的缓存中
步骤:
-
开启全局缓存 核心配置文件中
<settings> <setting name="cacheEnabled" value="true"/> </settings>
-
在当前mapper,xml 中使用二级缓存
<cache/>
-
也可以自定义
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
-
测试
@Test public void testCache(){ SqlSession session= MyBatisUtil.getSqlSession(); SqlSession session2= MyBatisUtil.getSqlSession(); UserMapper mapper=session.getMapper(UserMapper.class); UserMapper mapper2=session.getMapper(UserMapper.class); User user= mapper.queryUserById(1); System.out.println(user); System.out.println("=========================="); User user2= mapper2.queryUserById(1); System.out.println(user2); System.out.println(user==user2); session.close(); session2.close(); }
结果
[com.yw.dao.UserMapper]-Cache Hit Ratio [com.yw.dao.UserMapper]: 0.0 [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection [org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 915500720. [com.yw.dao.UserMapper.queryUserById]-==> Preparing: select * from mybatis.user where id=1 [com.yw.dao.UserMapper.queryUserById]-==> Parameters: [com.yw.dao.UserMapper.queryUserById]-<== Total: 1 User(id=1, name=小白, pwd=123456) ========================== [com.yw.dao.UserMapper]-Cache Hit Ratio [com.yw.dao.UserMapper]: 0.0 User(id=1, name=小白, pwd=123456) true [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@36916eb0] [org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 915500720 to pool.
-
问题
直接使用
<cache/>
需要将实体类 序列化
小结:
-
只要开启了二级缓存,在同一个Mapper 下就有效
-
所有的数据的都会先房在一级缓存中
14.5 自定义缓存 Ehcache
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。
导入
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
配置文件
<cache type="org.mybatis.caches.ehcache.EhcacheCache" />
新增 ehcache.xml
<?xml version="1.0" encoding="UTF-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>