MyBatis
感谢狂神的分享:B站视频地址
推荐文章:https://mp.weixin.qq.com/s/GgHU18NS_n2F1EE-gYPu7Q
官网地址:https://mybatis.org/mybatis-3/
github地址:https://github.com/search?q=mybatis
1、简介
什么是MyBatis
- MyBatis本是apache的一个开源项目
iBatis
,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。 - iBatis 一词来源于
internet
和abatis
的组合,是一个基于Java的持久层框架。iBatis提供的持久层框架包括SQL Maps和Data Access Objects - MyBatis 支持自定义 SQL、存储过程以及高级映射。
- MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
maven依赖
可以去maven库中,下载最新版:https://mvnrepository.com/
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>
MyBatis特点
- 简单易学
- 灵活: sql写在xml里,便于统一管理和优化。
- 解除sql与程序代码的耦合:sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的orm字段关系映射。
- 提供对象关系映射标签,支持对象关系组建维护。
- 提供xml标签,支持编写动态sql。
2、第一个MyBatis程序
1、创建数据库,建造实体类
CREATE DATABASE `mybatis`;
USE `mybatis`;
CREATE TABLE `user`(
`id` INT(20) NOT NULL PRIMARY KEY,
`name` VARCHAR(30) DEFAULT NULL,
`pwd` VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `user`(`id`,`name`,`pwd`) VALUES
(1,'狂神','123456'),
(2,'张三','123456'),
(3,'李四','123890')
public class User {
private int id;
private String name;
private String pwd;
//省略get/set方法、构造方法。。。
2、创建一个普通的maven项目,删除 src 文件夹,添加如下依赖:
<!--mysql 驱动-->
<dependencys>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencys>
<build>
<!--为防止mapper.xml文件无法输出,需进行如下配置-->
<resources>
<!-- src/main/java文件夹下,可以存放 *.properties、*.xml 文件,不用过滤 -->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
3、在父项目下,创建一个maven项目的 module,并在Resource下创建mybatis核心配置文件
<?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>
<!-- 可以配置多个数据源,默认指定development -->
<environments default="development">
<!-- 数据源: development -->
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/stu?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- mapper.xml都要在这里进行注册 -->
<mappers>
<mapper resource="com/kuang/mapper/UserMapper.xml"/>
</mappers>
</configuration>
4、创建mybatis工具类,返回SqlSessionFactory对象
public class MybatisUtils {
/*
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。
而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
*/
private static SqlSessionFactory sqlSessionFactory;
static{
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
/*
* 有了 SqlSessionFactory,就可以从中获得 SqlSession 的实例。
* SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
*/
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
5、编写持久层接口以及对应xml文件
public interface UserMapper {
//查询所有用户
List<User> selectUsers();
//根据名称模糊查询
List<User> selectUsersByName(String name);
//根据id查询用户
User getUserById(int id);
//新增一个用户
int addUser(User user);
//根据id修改用户
int updateUser(User user);
//根据id删除用户
int deleteUserById(int id);
}
<?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指定对应的dao/mapper接口-->
<mapper namespace="com.kuang.mapper.UserMapper">
<!--具体的方法,id 与接口中的方法名对应-->
<select id="selectUsers" resultType="com.kuang.pojo.User">
select * from user ;
</select>
<!--根据名称模糊查询-->
<select id="selectUsersByName" parameterType="string" resultType="com.kuang.pojo.User">
select * from user where name like concat('%',#{name},'%');
</select>
<!--根据id查询用户-->
<select id="getUserById" parameterType="int" resultType="com.kuang.pojo.User">
select * from user where id = #{id}
</select>
<!--新增一个用户-->
<insert id="addUser" parameterType="com.kuang.pojo.User">
insert user(id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
<!--修改一个用户-->
<update id="updateUser" parameterType="com.kuang.pojo.User">
update user set name = #{name},pwd = #{pwd} where id = #{id}
</update>
<!--删除一个用户-->
<delete id="deleteUserById" parameterType="int">
delete from user where id = #{id}
</delete>
</mapper>
6、编写测试类
@Test
public void getAllUser(){
//获取sqlSession
SqlSession session = MybatisUtils.getSqlSession();
//获取mapper
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用mapper定义的方法
List<User> userList = userMapper.selectUsers();
for (User user : userList) {
System.out.println(user.toString());
}
}
//新增一个用户,需要显式提交事务
@Test
public void addUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.addUser(new User(4, "haha", "root123"));
sqlSession.commit(); //增、删、改需要显式提交事务
System.out.println(i);
}
项目结构如下:
3、配置解析
官网配置文档解析:https://mybatis.org/mybatis-3/zh/configuration.html
属性(properties)
属性(如数据库连接属性)可以在外部进行配置,并可以进行动态替换:
环境配置(environments)
MyBatis 可以配置成适应多种环境,但每个 SqlSessionFactory 实例只能选择一种环境。 如果需要连接多个数据库,则每个数据库对应一个 SqlSessionFactory 实例。
环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID
<environments default="development"> <!-- 默认使用的环境 ID(比如:default="development") -->
<!-- 每个 environment 元素定义的环境 ID(比如:id="development") -->
<environment id="development">
<!-- 事务管理器的配置(比如:type="JDBC")
MyBatis 中有两种类型的事务管理器(type="[JDBC | MANAGED]")
-->
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<!-- 数据源的配置(比如:type="POOLED")
有三种内建的数据源类型(type="[UNPOOLED|POOLED|JNDI]")
-->
<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>
类型别名(typeAliases)
- 类型别名可为 Java 类型设置一个缩写名字。
- 意在降低冗余的全限定类名书写。
如下所示,可以在mapper.xml中,用user
代替com.kuang.pojo.User
<typeAliases>
<typeAlias alias="user" type="com.kuang.pojo.User"></typeAlias>
</typeAliases>
也可以指定一个包名,MyBatis 会在包名下面搜索 Java Bean,使用 Bean 的首字母小写的非限定类名来作为它的别名。
如下所示,用 user
作为 com.kuang.pojo.User
的别名:
<typeAliases>
<package name="com.kuang.pojo"></package>
</typeAliases>
设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下面是mybatis的所有设置及默认值:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
映射器(mappers)
MapperRegistry:注册绑定我们的Mapper文件。每一个Mapper.xml都需要在Mybatis核心配置文件中注册!
方式一:【推荐使用】
<mappers>
<mapper resource="com/kuang/mapper/UserMapper.xml"/>
</mappers>
方式二:使用class文件绑定注册
<mappers>
<mapper resource="com.kuang.mapper.UserMapper"/>
</mappers>
注意点:
- 接口和它的Mapper配置文件必须同名!
- 接口和它的Mapper配置文件必须在同一个包下!
方式三:使用扫描包进行注入绑定
<mappers>
<mapper resource="com.kuang.mapper"/>
</mappers>
注意点:
- 接口和它的Mapper配置文件必须同名!
- 接口和它的Mapper配置文件必须在同一个包下!
作用域(Scope)和生命周期
作用域和生命周期类别是至关重要的,错误的使用会导致非常严重的 并发问题
SqlSessionFactoryBuilder:一旦创建,就不再需要它了。最佳作用域是方法作用域(也就是局部方法变量)
SqlSessionFactory:一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
最佳作用域是应用作用域,最简单的就是使用单例模式
或者静态单例模式
。
SqlSession:
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。
每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它:
这里的每一个Mapper,就代表一个具体的业务!
4、ResultMap
解决 数据库字段名、实体类属性名 不一致问题,有以下两种方法:
采用mapUnderscoreToCamelCase属性设置(字段与属性有驼峰映射关系)
数据库字段:a_column ,实体类属性:aColumn
<settings>
<!--是否开启驼峰命名自动映射,即从数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
ResultMap (字段与属性,没有任何关联)
数据库与实体类如下:
数据库: id name pwd
java类:id name password
<!--结果集映射-->
<resultMap id="UserMap" type="User">
<!--column数据库中的字段,property实体类中的属性-->
<result column="id" property="id"/>
<result column="name" property="name"/>
<result column="pwd" property="password"/>
</resultMap>
<select id="getUserById" resultMap="UserMap">
select * from mybatis.user where id = #{id}
</select>
5、日志
日志工厂
Mybatis 通过使用内置的日志工厂提供日志功能,把日志工作委托给下面的实现之一:
SLF4J
Apache Commons Logging
Log4j 2
Log4j (3.5.9 起废弃)
JDK logging
通过mybatis-config.xml中的 setting 设置日志的具体实现。logImpl 可选的值有:SLF4J、LOG4J(3.5.9 起废弃)、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING,或者是实现了接口 org.apache.ibatis.logging.Log 的,且构造方法是以字符串为参数的类的完全限定名
STDOUT_LOGGING
标准日志输出,配置如下:
<settings>
<!-- 配置日志的具体实现 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
log4j2
Apache Log4j 2 是对 Log4j 的升级:官网地址
具体使用如下:引入jar包,并配置核心配置文件 log4j2.xml
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.x.x</version>
</dependency>
参考博客:log4j2.xml配置详解
最简配置:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
配置模板:
<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration monitorInterval="5">
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--变量配置-->
<Properties>
<!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
<!-- %logger{36} 表示 Logger 名字最长36个字符 -->
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
<!-- 定义日志存储的路径 -->
<property name="FILE_PATH" value="更换为你的日志路径" />
<property name="FILE_NAME" value="更换为你的项目名" />
</Properties>
<appenders>
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="${LOG_PATTERN}"/>
<!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
<File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">
<PatternLayout pattern="${LOG_PATTERN}"/>
</File>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
<!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
<!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
</appenders>
<!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
<!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.mybatis" level="info" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<!--监控系统信息-->
<!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
<Logger name="org.springframework" level="info" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<root level="info">
<appender-ref ref="Console"/>
<appender-ref ref="Filelog"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</loggers>
</configuration>
简单使用
1、在要使用Log4j2的类中,导入下面两个包:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger
2、日志对象,参数为当前类的class
private static Logger logger = LogManager.getLogger(UserTest.class);
3、日志级别
logger.info("info:进入了testLog4j");
logger.debug("DEBUG:进入了testLog4j");
logger.error("erro:进入了testLog4j");
6、分页
使用Limit分页
语法:SELECT * from user limit startIndex [,pageSize]
SELECT * from user limit 3 #[0,n]
mapper.xml中的下发:
<!-- 分页 -->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
RowBounds分页
- 接口
List<User> selectByRowBounds();
- mapper.xml
<select id="selectByRowBounds" resultMap="UserMap">
select * from mybatis.user
</select>
- 测试代码
@Test
public void getUserByRowBounds(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
//RowBounds实现
RowBounds rowBounds = new RowBounds(0, 2);
//通过java代码层面实现分页
List<User> userList = sqlSession.selectList("com.kuang.mapper.UserMapper.selectByRowBounds",null,rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
分页插件 PagerHleper
PageHelper 文档地址:https://pagehelper.github.io/docs/
7、注解开发
在真正的开发中,很多时候我们会选择面向接口编程。根本原因:** 解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得更容易,规范性更好**
- 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性和方法;
- 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现;
- 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构;
使用注解开发
1、在接口的方法上,使用注解编写sql
@Select("select * from user")
List<User> getUsers();
//CRUD
/*
基本类型的参数或者String类型,需要加上
引用类型不需要加
如果只有一个基本类型的话,可以忽略,但是建议都加上!
在SQL中引用的,就是@Param("")中设定的属性名!
*/
@Select("select * from user where id=#{id}")
User getUserById(@Param("id") int id);
@Insert("insert into user (id,name,pwd) values(#{id},#{name},#{password})")
int addUser(User user);
@Update("update user set name=#{name},pwd=#{password} where id=#{id}")
int updateUser(User user);
@Delete("delete from user where id = #{uid}")
int deleteUser(@Param("uid") int id);
2、在mybatis-config.xml中,注册接口
<mappers>
<mapper class="com.kuang.dao.UserMapper"/>
</mappers>
注:本质是反射机制,低层实现了动态代理
8、mybatis执行流程
参考博客:SqlSessionTemplate、sqlSessionFactory、sqlSessionFactoryBean、SqlSessionManager
9、Lombok
1、在IDEA中安装Lombok插件!
2、在项目pom.xml文件中导入Lombok的jar包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>xx.xx.xx</version>
</dependency>
3、在实体类上加注解
@Getter and @Setter //添加到属性
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor //构造方法
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data //添加到实体类:无参构造、get、set、toString、hashCode、equals
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
10、多对一
1、创建数据库
-- 创建teacher表,并插入数据
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,'秦老师');
-- 创建student表,建立外检关联,并插入数据
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');
2、实体类
//学生类:
public class Student {
private int id;
private String name;
private Teacher teacher; //使用老师类,作为属性
}
//老师类
public class Student {
private int id;
private String name;
}
按照查询嵌套处理
如下所示,getAllStu
查询中,嵌套了getAllTeacher
查询。
类似子查询,先查出来student的结果集,再查询teacher
<!--
思路:
1.查询所有的学生信息
2.根据查询出来的学生的tid,寻找对应的老师! 子查询
-->
<select id="getAllStu" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--
复杂的属性,需要单独处理
对象:association 集合:collection
-->
<association property="teacher" column="tid" javaType="Teacher" select="getAllTeacher"/>
</resultMap>
<select id="getAllTeacher" resultType="Teacher">
select * from teacher where id = #{id}
</select>
按照结果嵌套处理
连表查询,查出结果之后,映射在 resultMap中
<select id="getAllStu2" resultMap="StudentTeacher2">
select s.id sId,s.name sName,t.name tName
from student s
LEFT JOIN teacher t on s.tid = t.id
</select>
<!-- 将查询的结果,嵌套映射在不同的实体类上 -->
<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sId"/>
<result property="name" column="sName"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tName"/>
</association>
</resultMap>
11、一对多
根据查询结果映射(连表查询)
<!-- 先查询出来结果,再映射到集合中 -->
<select id="getTeacherById" resultMap="TeacharStudent">
select t.id tId,t.name tName,s.id sId,s.name sName
from teacher t
LEFT JOIN student s on t.id = s.tid
where t.id = #{tId}
</select>
<resultMap id="TeacharStudent" type="Teacher">
<result column="tId" property="id"/>
<result column="tName" property="name"/>
<!--集合中的泛型信息,通过ofType获取-->
<collection property="studentList" ofType="Student">
<result column="sId" property="id"/>
<result column="sName" property="name"/>
</collection>
</resultMap>
根据查询映射(子查询)
<!-- 通过集合,进行关联查询 -->
<select id="getTeacherById2" resultMap="TeacharStudent2">
select * from teacher where id = #{tId}
</select>
<resultMap id="TeacharStudent2" type="Teacher">
<result column="id" property="id"/>
<result column="name" property="name"/>
<collection property="studentList" javaType="ArrayList" ofType="Student" select="selectStuByTid" column="id"/>
</resultMap>
<select id="selectStuByTid" resultType="Student">
select * from student where tid = #{id}
</select>
12、动态sql
动态SQL就是指根据不同的条件生成不同的SQL语句
if
<select id="findActiveBlogWithTitleLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = 'ACTIVE'
<if test="title != null">
AND title like #{title}
</if>
</select>
choose、when、otherwise
有时候,只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch
语句。
传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找。
若两者都没有传入,就返回标记为 featured = 1 的 BLOG
注:只会执行其中一个条件
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
trim、where、set
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
属性 | 描述 |
---|---|
prefix | 给sql语句拼接的前缀 |
suffix | 给sql语句拼接的后缀 |
prefixOverrides | 去除sql语句前面的关键字或者字符,由管道符| 分隔。假设该属性指定为"AND",当sql语句的开头为"AND",将被去除。 |
suffixOverrides | 去除sql语句后面的关键字或者字符,由管道符 | 分隔。 |
-- 指定sql片段以 where 开始,如果where 后面紧跟 (and、or),则被删除
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
set 元素可以用于动态包含需要更新的列,忽略其它不更新的列
-- set元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT * FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>
sql 片段
使用SQL标签抽取公共的部分
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
在需要使用的地方使用Include标签引用即可
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
13、缓存
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。
Mybatis系统中默认定义了两级缓存:
-
一级缓存,默认开启(SqlSession级别的缓存,也称为本地缓存)
-
二级缓存,需手动开启和配置(基于namespace级别的缓存)
-
为了提高扩展性,Mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存。
一级缓存
一级缓存也叫本地缓存,默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个阶段有效:
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 以后如果需要获取相同的数据,直接从缓存中拿,没有必要再去查询数据库
以下几种情况,会导致缓存失效:
- 查询不同的数据
- 增删改操作,可能会改变原来的数据,所以必定会刷新缓存
- 查询不同的Mapper
- 手动清理缓存:
sqlSession.clearCache()
二级缓存
二级缓存也叫全局缓存,一级缓存作用于太低,所以诞生了二级缓存;
基于namespace级别的缓存,一个名称空间,对应一个二级缓存
工作机制:
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
- 当前会话关闭,对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在自己对应的缓存(map)中
步骤:
- 开启全局缓存
<!--开启全局缓存-->
<setting name="cacheEnable" value="true"/>
- 在要使用二级缓存的Mapper中开启
下面更高级的配置创建了一个 FIFO 缓存,
每隔 60 秒刷新,
最多可以存储结果对象或列表的 512 个引用,
而且返回的对象被认为是只读的
因此对它们进行修改可能会在不同线程中的调用者产生冲突。
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
- 每条语句可以自定义与缓存交互的方式,或将它们完全排除于缓存之外,这可以通过在每条语句上使用两个简单属性来达成。
默认情况下,语句会这样来配置:
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
总结:
- pojo类要实现序列化接口
- 只要开启了二级缓存,只在同一个Mapper下有效
- 所有的数据都会先放在一级缓存中;只有当会话提交或者关闭的时候,才会提交到二级缓存中
缓存原理
自定义缓存
Ehcache是一种广泛使用的开源Java分布式缓存,主要面向通用缓存
- 导入包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
- ehcache的配置文件
<?xml version="1.0" encoding="UTF8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--
diskStore :为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置,参数解释如下:
user.hoeme - 用户主目录
user.dir - 用户当前工作目录
javaio.tmpdir - 默认临时文件
-->
<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>
<!--
defaultCache:默认缓存策略,当cache找不到定义的缓存时,则使用这个缓存策略,只能定义一个
-->
<!--
name:缓存名称。
maxElementsInMemory:缓存最大个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxElementsOnDisk:硬盘最大缓存个数。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
-->
- 使用ehcache
<!--自定义缓存,用第三方缓存覆盖-->
<cache type = "org.mybatis.caches.ehcache.EhcacheCache"/>
- 但想要在多个命名空间中共享相同的缓存配置和实例,可以使用 cache-ref 元素来引用另一个缓存。
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
除了上述自定义缓存的方式,你也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。
<cache type="com.domain.something.MyCustomCache"/>
type 属性指定的类必须实现 org.apache.ibatis.cache.Cache
接口,且提供一个接受 String 参数作为 id 的构造器。 这个接口是 MyBatis 框架中许多复杂的接口之一,但是行为却非常简单。
public interface Cache {
String getId();
int getSize();
void putObject(Object key, Object value);
Object getObject(Object key);
boolean hasKey(Object key);
Object removeObject(Object key);
void clear();
}