Mybatis
Mybatis
简介
- 持久层框架
- 把一些东西长久得存下来
- 将数据存入数据库
- 没有也行,有了更方便
依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
优点
- SQL与代码分离
- 提供映射标签,支持与数据库字段关系映射
- 提供对象关系映射标签
- 提供XML标签,可编写动态SQL
HelloWorld
- *Mapper.xml
- resultType
- 返回单个对象
- resultMap
- 返回多个对象
- resultType
初始配置与实现
数据库表构造
CREATE DATABASE `mybatis`;
USE `mybatis`;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(20) NOT NULL,
`name` varchar(30) DEFAULT NULL,
`pwd` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `user`(`id`,`name`,`pwd`) values (1,'狂神','123456'),(2,'张三','abcdef'),(3,'李四','987654');
流程:
- pom.xml
- MybatisUtil.java
- mybatis-config.xml
- User.java
- UserDao.java
- UserMapper.xml
- Test.java
pom.xml
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.32</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
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.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?autoReconnect=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="lu123"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/lu/dao/UserMapper.xml"/>
</mappers>
</configuration>
UserMapper.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.lu.dao.UserDao">
<select id="getUserList" resultType="com.lu.pojo.User">
select * from mybatis.user
</select>
</mapper>
MybatisUtil.java
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 MyBatisUtil {
private static SqlSessionFactory factory;
static {
try {
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
factory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static SqlSession getSqlSession() {
return factory.openSession();
}
}
测试
public class UserDaoTest {
@Test
public void test() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
for (User user : userDao.getUserList()) {
System.out.println(user);
}
sqlSession.close();
}
}
Map妙用
- 可以脱离变量命名的束缚
- 当变量很多时,便于操作
User getUserByMap(Map<String, Object> map);
<select id="getUserByMap" parameterType="map" resultType="com.lu.pojo.User">
select * from mybatis.user where id = #{id} and name = #{name}
</select>
@Test
public void test06() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("id", 1);
map.put("name", "lct");
System.out.println(userMapper.getUserByMap(map));
sqlSession.close();
}
模糊查询
List<User> getUserThroughBlurSearch(String name);
<select id="getUserThroughBlurSearch" parameterType="string" resultType="com.lu.pojo.User">
select * from mybatis.user where name like #{name}
</select>
@Test
public void test07() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
for (User user : userMapper.getUserThroughBlurSearch("%李%")) {
System.out.println(user);
}
sqlSession.close();
}
问题
增删改未能生效
- 需要提交事务
commit
XML读取不到
pom.xml
中添加语句
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>**/*.properties</exclude>
<exclude>**/*.xml</exclude>
</excludes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
配置解析
mybatis-config.xml
标签顺序
properties?
settings?
typeAliases?
typeHandlers?
objectFactory?
objectWrapperFactory?
reflectorFactory?
plugins?
environments?
databaseIdProvider?
mappers
环境配置
environments
- 尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境
属性
properties
- 可以在外部进行配置,并可以进行动态替换
- 既可以在典型的 Java 属性文件中配置这些属性
- 也可以在 properties 元素的子元素中设置
properties样例
# 这里的优先使用
driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/mybatis?autoReconnect=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username = root
password = lu123
<properties resource="db.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
类型别名
typeAliases
- 设置一个短一点的名字
- 一个包扫描之后,它里面的类无法再起别名
- 因此样例中只能选其一,另一个需要注释掉
typeAliases样例
<typeAliases>
<package name="com.lu.pojo"/>
<typeAlias type="com.lu.pojo.User" alias="user"/>
</typeAliases>
或者
@Alias("") // 扫描包后通过注解起别名
设置
settings
- logImpl
- 指定 MyBatis 所用日志的具体实现,未指定时将自动查找
- SLF4J | LOG4J(3.5.9 起废弃) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
生命周期和作用域
SqlSessionFactoryBuilder
一旦创建了 SqlSessionFactory,就不再需要SqlSessionFactoryBuilder了
- 最佳作用域是方法作用域(也就是局部方法变量)
SqlSessionFactory
SqlSessionFactory就是数据库连接池,一旦创建就要一直存在,不要反复创建
- 最佳作用域是应用作用域(全局)
SqlSession
- 连接到连接池的一个请求
- 用完之后需要关闭
- 最佳作用域是方法作用域(也就是局部方法变量)
resultMap
解决类的属性名和数据库字段名不一致的问题
- 就很强大
- 要是人们不和也可以这样解决就好了
// 数据库中,密码列名为:pwd
public class User {
private int id; //id
private String name; //姓名
private String password; //密码
}
<mapper namespace="com.lu.dao.UserMapper">
<resultMap id="UserMap" type="user">
<result column="id" property="id"/>
<result column="name" property="name"/>
<result column="pwd" property="password"/>
</resultMap>
<select id="getUserList" resultMap="UserMap">
select * from mybatis.user
</select>
</mapper>
日志
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
log4j.properties
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/lu.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
- mybatis 3.5.9之后无法使用log4j
- log4j2用不来
- 所以摆了
- 但把mybatis降级后可以用,所以还是没摆
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
使用
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
static Logger logger = Logger.getLogger(UserDaoTest.class);
@Test
public void test02() {
logger.info("info: enter");
logger.debug("debug: enter");
logger.error("error: enter");
}
分页
- 减少数据处理量
List<User> getUserByLimit(Map<String, Object> map);
<select id="getUserByLimit" resultMap="UserMap" parameterType="map">
select * from mybatis.user limit #{idx}, #{size}
</select>
@Test
public void test03() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("idx", 0);
map.put("size", 3);
List<User> users = mapper.getUserByLimit(map);
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
面向接口开发
@Select("select * from user")
List<User> getUserList();
<mappers>
<mapper class="com.lu.dao.UserMapper"/>
</mappers>
@Test
public void test() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
for (User user : mapper.getUserList()) {
System.out.println(user);
}
sqlSession.close();
}
- 基本数据类型对于变量名建议加上
@Param("")
- 引用类型不需要加
- 类(class)、 接口(interface)、数组(array)
#与$的区别
#{}
的作用主要是替换预编译语句(PrepareStatement)中的占位符(?)
INSERT INTO user (name) VALUES (#{name});
INSERT INTO user (name) VALUES (?);
${}
的作用是直接进行字符串替换
INSERT INTO user (name) VALUES ('${name}');
INSERT INTO user (name) VALUES ('kuangshen');
执行流程
- Resources获取加载全局配置文件
- 实例化SqlSessionFactoryBuilder构造器
- 解析配置文件流XMLConfigBuilder
- Configuration所有的配置信息
- SqlSessionFactory实例化
- transactionCacheManager事务缓存管理器
- 创建executor执行器
- 创建SqlSession
- Mapper,实现CRUD
- 若失败,返回6
- 若成功,提交事务,到10
- 关闭
Lombok
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
多对一
- 集合:
collection
- 对象集合:
ofType
- 对象集合:
- 对象:
association
- 归根结底就是处理对象和数据库之间的字段属性冲突
初始
用到的表
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
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
private Teacher teacher;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private int id;
private String name;
}
查询嵌套方式
List<Student> getStudent();
<resultMap id="stuMap" type="student">
<association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
</resultMap>
<select id="getStudent" resultMap="stuMap">
select * from mybatis.student
</select>
<select id="getTeacher" resultType="teacher">
select * from mybatis.teacher where id = #{tid}
</select>
结果嵌套方式
List<Student> getStudentByResult();
<resultMap id="stuResult" type="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
<select id="getStudentByResult" resultMap="stuResult">
select s.id sid, s.name sname, t.name tname
from mybatis.student s, mybatis.teacher t
where s.tid = t.id
</select>
- 查询嵌套不需要把类的所有属性表明,结果嵌套需要,否则对应对象的该属性会为空
一对多
初始
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private String name;
private int tid;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
实现
Teacher getTeacher(@Param("tid") int id);
<resultMap id="teacherMap" type="teacher">
<result property="id" column="t_id"/>
<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>
<select id="getTeacher" resultMap="teacherMap">
select s.id sid, s.name sname, t.name tname, t.id t_id
from mybatis.student s, mybatis.teacher t
where s.tid = t.id and t.id = #{tid}
</select>
- t.id 为什么不定义为tid
- 如果那样定义的话,可能会与student库中的tid字段冲突
- 并不会报错,但最好不要这样写,容易有歧义
动态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;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
}
int addBlog(Blog blog);
<insert id="addBlog" parameterType="blog">
insert into mybatis.blog values (#{id}, #{title}, #{author}, #{createTime}, #{views})
</insert>
@Test
public void test01() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IDUtil.getID());
blog.setTitle("Mybatis如此简单");
blog.setAuthor("lct");
blog.setCreateTime(new Date());
blog.setViews(9999);
mapper.addBlog(blog);
blog.setId(IDUtil.getID());
blog.setTitle("Java如此简单");
mapper.addBlog(blog);
blog.setId(IDUtil.getID());
blog.setTitle("Spring如此简单");
mapper.addBlog(blog);
blog.setId(IDUtil.getID());
blog.setTitle("微服务如此简单");
mapper.addBlog(blog);
sqlSession.commit();
sqlSession.close();
}
if
List<Blog> queryBlogIf(Map<String, Object> map);
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
@Test
public void test02() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("author", "lct");
map.put("title", "Mybatis如此简单");
for (Blog blog : mapper.queryBlogIf(map)) {
System.out.println(blog);
}
sqlSession.close();
}
choose (when, otherwise)
- 类似于Java中的
switch
语句 - 样例中,虽然设置了
author
,但由于when
的执行顺序,进行对比的依旧是title
List<Blog> queryBlogChoose(Map<String, Object> map);
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<choose>
<when test="title != null">
and title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views >= 4000
</otherwise>
</choose>
</where>
</select>
@Test
public void test03() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("author", "lct");
map.put("title", "Mybatis如此简单");
for (Blog blog : mapper.queryBlogChoose(map)) {
System.out.println(blog);
}
sqlSession.close();
}
trim (where, set)
-
where
标签下的语句前面都加and
最健壮 -
set
标签下的语句后面都加,
最健壮 -
trim
是以上两个标签的底层实现-
<trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim> <trim prefix="SET" suffixOverrides=","> ... </trim>
-
int updateBlog(Map<String, Object> map);
<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>
and id = #{id}
</where>
</update>
@Test
public void test04() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("author", "lct");
map.put("title", "Mybatis如此简单");
map.put("id", "4d08e2312bc3405195b2c581e63343f3");
mapper.updateBlog(map);
sqlSession.commit();
sqlSession.close();
}
SQL片段
- 将一些可重复使用的SQL语句存起来
- 可以引入使用
- 建议基于单表操作
- 不要存在
where
标签
<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"/>
</where>
</select>
foreach
List<Blog> queryBlogForeach(Map<String, Object> map);
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
id in
<foreach item="id" collection="list" open="(" separator="," close=")">
#{id}
</foreach>
</where>
</select>
@Test
public void test05() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String, Object> map = new HashMap<>();
List<String> list = new ArrayList<>();
list.add("4d08e2312bc3405195b2c581e63343f3");
list.add("5bf1b702d0df46d4970ff7401e026345");
map.put("list", list);
for (Blog blog : mapper.queryBlogForeach(map)) {
System.out.println(blog);
}
sqlSession.close();
}
缓存
- 一次查询的结果暂存到一个可以直接读取的地方
- 再次查询相同数据就去取就可以,不用走数据库
适用于:经常需要查询但几乎不会改变的数据
最终目的:提高查询效率
一级缓存
相当于一个Map
- 默认开启,且仅在方法内有效
- 以SqlSession为例,从创建到用完关闭这段时间,就是一级缓存发挥作用的时候
- 在一个Session中查询两次相同记录得到的对象相同
- 走了一级缓存
- 如果中间存在
insert, update, delete
等操作,commit
后会刷新缓存 - 此时,查询两次相同记录得到的对象不同
对象不同
public static void main(String[] args) {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.getUserByID(1);
User newUser = new User(4, "李五", "1235");
mapper.updateUser(newUser);
sqlSession.commit();
User user2 = mapper.getUserByID(1);
System.out.println(user1 == user2);
sqlSession.close();
}
对象相同
public static void main(String[] args) {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.getUserByID(1);
User user2 = mapper.getUserByID(1);
System.out.println(user1 == user2);
sqlSession.close();
}
二级缓存
作用域为一个Mapper
,即namespace
- 在全局配置中添加
setting
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="cacheEnabled" value="true"/>
</settings>
cache
后面的属性可加可不加
<mapper namespace="com.lu.dao.UserMapper">
<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<select id="getUserByID" parameterType="int" resultType="user">
select * from mybatis.user where id = #{uid}
</select>
</mapper>
- 当一级缓存失效(
SqlSession
关闭),二级缓存才开始起作用
public static void main(String[] args) {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
SqlSession session = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.getUserByID(1);
sqlSession.close();
UserMapper sessionMapper = session.getMapper(UserMapper.class);
User user2 = sessionMapper.getUserByID(1);
session.close();
System.out.println(user1 == user2);
}
缓存顺序
查看
- 用户首先查看二级缓存中有没有
- 再看一级缓存有没有
- 最后在数据库查询
生成
- 从数据库查询到信息
- 保存在SqlSession中(一级缓存)
- SqlSession关闭后,上提到二级缓存
Ehcache
浅尝辄止,知道有这个东西就行了,就不记录了