基本信息
MyBatis 本是 apache 的一个开源项目 iBatis , 2010年这个项目由 apache software foundation 迁移到了 google code,并且改名为 MyBatis 。2013年11月迁移到 Github。
iBATIS 一词来源于“ internet ”和“ abatis ”的组合,是一个基于 Java 的持久层(数据访问层)框架。
当前,最新版本是 MyBatis 3.5.7 ,其发布时间是2021年4月21日。
MyBatis 是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Ordinary Java Objects,普通的 Java 对象)映射成数据库中的记录。
MyBatis 应用程序大都使用 SqlSessionFactory 实例,SqlSessionFactory 实例可以通过 SqlSessionFactoryBuilder 获得,而 SqlSessionFactoryBuilder 则可以从一个 XML 配置文件或者一个预定义的配置类的实例获得。
第一个 MyBatis 程序(增删改查)
- 数据库建表
CREATE TABLE `tb_person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
INSERT INTO `example`.`tb_person`(`username`, `password`) VALUES ('zhangsan', '123')
INSERT INTO `example`.`tb_person`(`username`, `password`) VALUES ('lisi', '234')
INSERT INTO `example`.`tb_person`(`username`, `password`) VALUES ('zhaoliu', '345')
- 创建 maven 工程
- 导入 junit,mysql,mybatis 坐标
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
- src /main /resources 下创建核心配置文件 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核心配置文件-->
<configuration>
<!--设置别名简化 resultType 书写,该标签必须在 environments 前-->
<typeAliases>
<!--1、单个别名的定义: alias:别名,type:别名映射的类型-->
<typeAlias type="com.mybatis.entity.User" alias="User"/>
<!--2、批量别名定义:指定包路径,自动扫描包下边的类,定义别名,别名默认为类名(首字母小写或大写)
<package name="com.mybatis.entity"/> -->
</typeAliases>
<!--environments配置环境组-->
<!--default默认环境-->
<!-- 配置开发环境,可以配置多个,在具体用时再做切换 -->
<environments default="test">
<!--environment单个环境-->
<environment id="test">
<!--transactionManager配置事务管理器-->
<!-- 事务管理类型:JDBC、MANAGED -->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池-->
<!-- 数据源类型:POOLED、UNPOOLED、JNDI -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/example?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&allowMultiQueries=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--每一个Mapper.xml需要在Mybatis核心配置文件中注册-->
<mappers>
<!-- 路径用 斜线(/) 分割,而不是用 点(.) -->
<mapper resource="com/mybatis/dao/UserMapper.xml"></mapper>
</mappers>
</configuration>
- 编写 MyBatis 工具类
package com.mybatis.util;
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 MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
// 获取 SqlSessionFactory 实例
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();
}
}
- 编写代码
实体类
package com.mybatis.entity;
public class User {
private int id;
private String username;
private String password;
public User() {
}
public User(int id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
dao 层接口
package com.mybatis.dao;
import com.mybatis.entity.User;
import java.util.List;
public interface UserDao {
List<User> getUserList();
User getUserById(int id);
int addUser(User user);
int updateUser(User user);
int deleteUserById(int id);
}
- 创建映射文件 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">
<!--指定唯一 namespace ,一般习惯为接口的包路径名-->
<mapper namespace="com.mybatis.dao.UserDao">
<!-- 查询功能,resultType 设置返回值类型 -->
<select id="getUserList" resultType="User">
<!-- 书写 SQL 语句 -->
SELECT * FROM tb_person;
</select>
<select id="getUserById" parameterType="int" resultType="User">
SELECT *
FROM tb_person
WHERE id = #{id};
<!--parameterType 是简单类型( java 8 种原始数据类型加 string ),占位符 #{} 变量名任取-->
</select>
<!--以实体来封装参数 -->
<insert id="addUser" parameterType="User">
INSERT INTO tb_person (id, username, password)
VALUES (#{id}, #{username}, #{password});
<!--parameterType 是对象或 map,占位符 #{} 变量名为对象属性或 map 的 key 值-->
</insert>
<update id="updateUser" parameterType="User">
UPDATE tb_person
set username=#{username},
password=#{password}
WHERE id = #{id};
</update>
<delete id="deleteUserById" parameterType="integer">
DELETE
FROM tb_person
WHERE id = #{id};
</delete>
</mapper>
在 pom.xml 添加以下代码,否则非 resources 文件夹下的 xml 文件和 properties 无法导出
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
Mybatis配置错误:java.lang.ExceptionInInitializerError 解决
- 创建测试类
package com.mybatis.dao;
import com.mybatis.entity.User;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
import static com.mybatis.util.MyBatisUtils.getSqlSession;
public class testUserDao {
@Test
public void testGetUserList() {
SqlSession sqlSession = getSqlSession();
// 调用 mapper 中的方法:namespace + id
List<User> userList = sqlSession.selectList("getUserList");
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
@Test
public void testGetUserByUserId() {
SqlSession sqlSession = getSqlSession();
User user = sqlSession().selectOne("getUserById", 1);
System.out.println(user);
sqlSession.close();
}
@Test
public void testAddUser() {
SqlSession sqlSession = getSqlSession();
User user = new User(6, "xiaohong", "1234");
int res = sqlSession.insert("addUser", user);
if (res > 0) {
System.out.println("插入成功");
}
// 进行 insert,update,delete 操作时,需要手动提交,否则 res > 0 但数据库没有更新数据
sqlSession.commit();
sqlSession.close();
}
@Test
public void testUpdateUser() {
SqlSession sqlSession = getSqlSession();
User user = new User(6, "xiaohong", "3456");
int res = sqlSession.update("updateUser", user);
if (res > 0) {
System.out.println("更新成功");
}
sqlSession.commit();
sqlSession.close();
}
@Test
public void testDeleteUserById() {
SqlSession sqlSession = getSqlSession();
int res = sqlSession.delete("deleteUserById", 1);
if (res > 0) {
System.out.println("删除成功");
}
sqlSession.commit();
sqlSession.close();
}
}
log4j
日志,方便排错
- 导入坐标
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- src /main /resources 下创建 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=[%5p] [%c]%m%n
#文件输出的相关设置
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/mxt.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss,SSS}] [%5p] [%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
运行测试类
基于 Mapper 代理的示例
@Test
public void testGetUSerList2() {
SqlSession sqlSession = getSqlSession();
// 获得 mapper 代理对象
UserDao mapper = sqlSession.getMapper(UserDao.class);
// 直接调用接口中的方法,不同于向 sqlSession 传 SQL id
List<User> userList = mapper.getUserList();
userList.forEach(System.out::println);
}
需要遵循
- 接口的全限定名,要和 mapper.xml 的 namespace 属性一致
- 接口中的方法名要和 mapper.xml 中的 SQL 标签的 id 一致
- 接口中的方法入参类型,要和 mapper.xml 中 SQL 语句的入参类型一致
- 接口中的方法出参类型,要和 mapper.xml 中 SQL 语句的返回值类型一致
使用 map 为参数类型时
int addUser2(Map<String,Object> map);
<!--传递map中的key-->
<insert id="addUser2" parameterType="map">
insert into tb_person (id, username, password)
values (#{userId}, #{username}, #{password});
</insert>
@Test
public void addUser2(){
SqlSession sqlSession = getSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
HashMap<String, Object> map = new HashMap<>();
map.put("userId",9);
map.put("username","uhhhhh");
map.put("password","23333");
int res = mapper.addUser2(map);
if (res > 0) {
System.out.println("插入成功");
}
sqlSession.commit();
sqlSession.close();
}
模糊查询
<select id="getUserByName" parameterType="string" resultType="User">
SELECT * FROM tb_person WHERE username like concat("%", #{name}, "%") ;
</select>
mybatis-config.xml 配置解析
配置文件中,各个标签要按照如下顺序进行配置,因为mybatis 加载配置文件的源码中是按照这个顺序进行解析的
<configuration> 配置
<!-- 配置顺序如下
properties 属性
settings 设置
typeAliases 类型别名
typeHandlers 类型处理器
objectFactory 对象工厂
plugins 插件
environments 环境配置
environment 环境变量
transactionManager 事务管理器
dataSource 数据源
mappers 映射器
-->
</configuration>
- < properties >
一般将数据源的信息单独放在一个 properties 文件中,然后用这个标签引入,在下面 dataSource 标签中,就可以用 ${} 占位符快速获取数据源的信息
db.url=jdbc:mysql://127.0.0.1:3306/example?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
db.user=root
db.password=root
db.driver=com.mysql.cj.jdbc.Driver
<dataSource type="POOLED">
<!-- 从配置文件中加载属性 -->
<property name="driver" value="${db.driver}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.user}"/>
<property name="password" value="${db.password}"/>
</dataSource>
-
< settings >
用来开启或关闭 mybatis 的一些特性,比如可以用<setting name="lazyLoadingEnabled" value="true"/>
来开启延迟加载,可以用<settings name="cacheEnabled" value="true"/>
来开启二级缓存 -
< typeAliases >
设置别名
批量设置别名时如果要自定义别名可以在实体类上添加 @Alias 注解
@Alias(“xxx”)
public class xxx {
…
}
- < typeHandlers >
用于处理 Java 类型和 Jdbc 类型之间的转换,mybatis 有许多内置的 TypeHandler ,比如 StringTypeHandler ,会处理 Java 类型 String 和 Jdbc 类型 CHAR 和 VARCHAR 。这个标签用的不多 - < objectFactory >
mybatis 会根据 resultType 或 resultMap 的属性来将查询得到的结果封装成对应的 Java 类,它有一个默认的 DefaultObjectFactory ,用于创建对象实例,这个标签用的也不多 - < plugins >
可以用来配置 mybatis 的插件,比如在开发中经常需要对查询结果进行分页,就需要用到 pageHelper 分页插件
<!-- PageHelper 分页插件 -->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
- < environments >
配置数据源 - < mappers >
用来配置 mapper.xml 映射文件,这些 xml 文件里都是 SQL 语句
生命周期
动态 SQL
动态 SQL:根据不同的条件生成不同的 SQL 语句
- if
<select id="find" parameterType="User" resultType="User" >
SELECT * FROM tb_person WHERE id >= 2
<if test="username != null and username != ''">
AND username like concat("%", #{username}, "%");
</if>
</select>
当满足 test 条件时,才会将< if >标签内的 SQL 语句拼接上去
- choose( when,otherwise) 类似 switch
<select id="queryUserChoose" parameterType="map" resultType="User">
select * from tb_person
<where>
<choose>
<when test="username !=null">
username=#{username}
</when>
<when test="password !=null">
and password=#{password}
</when>
<otherwise>
and id=#{id}
</otherwise>
</choose>
</where>
</select>
- set
<update id="updateUser" parameterType="map">
update tb_person
<set>
<if test="username !=null">
username=#{username},
</if>
<if test="password !=null">
password=#{password}
</if>
</set>
where id=#{id}
</update>
- trim
以下 trim 可代替 where
<trim prefix="WHERE" prefixOverrides="AND | OR">
...
</trim>
以下 trim 可代替 set
<trim prefix="SET" prefixOverrides=",">
...
</trim>
- foreach
<select id="batchFindUser" parameterType="list" resultType="user" >
SELECT * FROM student WHERE id in
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<select id="batchFindUser" parameterType="list" resultType="user" >
SELECT * FROM tb_person
<where>
<foreach collection="list" item="id" open="(" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
- sql
可将重复的 SQL 片段(尽量只有 if )提取出来,然后在需要的地方,使用 < include > 标签进行引用
<sql id="if-username-password">
<if test="username != null">
username = #{username}
</if>
<if test="password != null">
and password = #{password}
</if>
</sql>
<select id="queryUserIf" parameterType="map" resultType="User">
SELECT * FROM tb_person
<where>
<include refid="if-username-password"></include>
</where>
</select>
多对一处理
多个学生对应一个老师
CREATE TABLE `tb_teacher` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
INSERT INTO `example`.`tb_teacher`(`id`, `name`) VALUES (1, 'hhhh')
CREATE TABLE `tb_student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`tid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `tid` (`tid`),
CONSTRAINT `tb_student_ibfk_1` FOREIGN KEY (`tid`) REFERENCES `tb_teacher` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
INSERT INTO `example`.`tb_student`(`id`, `name`, `tid`) VALUES (1, '小一', 1)
INSERT INTO `example`.`tb_student`(`id`, `name`, `tid`) VALUES (2, '小二', 1)
INSERT INTO `example`.`tb_student`(`id`, `name`, `tid`) VALUES (3, '小三', 1)
INSERT INTO `example`.`tb_student`(`id`, `name`, `tid`) VALUES (4, '小四', 1)
INSERT INTO `example`.`tb_student`(`id`, `name`, `tid`) VALUES (5, '小五', 1)
子查询
<?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.mybatis.dao.StudentDao">
<!--查询学生信息,并根据 tid 找到对应老师-->
<select id="getStudent" resultMap="StudentTeacher">
SELECT * FROM tb_student;
</select>
<resultMap id="StudentTeacher" type="com.mybatis.entity.Student">
<result property="id" column="id"></result>
<result property="name" column="name"></result>
<!--复杂的属性
对象:association
集合:collection-->
<association property="teacher" column="tid" javaType="com.mybatis.entity.Teacher" select="getTeacher"></association>
</resultMap>
<select id="getTeacher" resultType="com.mybatis.entity.Teacher">
SELECT * FROM tb_teacher WHERE id=#{id};
</select>
</mapper>
- resultType:返回类型
- resultMap:描述如何将结果集映射到 java 对象,属性有 id 和 type
- id:唯一标识,column 表示从数据库查询的属性,property 表示把该属性赋给实体对象的哪个属性
- result:column 表示从数据库查询的属性,property 表示把该属性赋给实体对象的哪个属性
- association:连接另一个表,javaType表示指定实体类属性的类型,property 表示指定的实体类属性
- collection:连接另一个表,property 表示指定的实体类属性,javaType表示指定实体类属性的类型,ofType表示映射到 List 或集合中的对象类型
多表查询
<select id="getStudent2" resultMap="StudentTeacher2">
SELECT s.id sid,s.name sname,t.name tname,t.id tid
FROM tb_student s,tb_teacher t
WHERE s.tid=t.id;
</select>
<resultMap id="StudentTeacher2" type="com.mybatis.entity.Student">
<id property="id" column="sid"></id>
<result property="name" column="sname"></result>
<association property="teacher" javaType="com.mybatis.entity.Teacher">
<result property="id" column="tid"></result>
<result property="name" column="tname"></result>
</association>
</resultMap>
一对多处理
一个老师对应多个学生
<?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.mybatis.dao.TeacherDao">
<select id="getTeacher" resultMap="TeacherStudent" parameterType="int">
SELECT * FROM tb_teacher WHERE id=#{tid};
</select>
<resultMap id="TeacherStudent" type="com.mybatis.entity.Teacher">
<id property="id" column="id"></id>
<result property="name" column="name"></result>
<collection property="studentList" column="id" javaType="ArrayList" ofType="com.mybatis.entity.Student" select="getStudentByTeacherId"></collection>
</resultMap>
<select id="getStudentByTeacherId" resultType="com.mybatis.entity.Student">
SELECT * FROM tb_student WHERE tid=#{tid};
</select>
<select id="getTeacher2" resultMap="TeacherStudent2" parameterType="int">
SELECT s.id sid,s.name sname,t.id tid,t.name tname
FROM tb_student s,tb_teacher t
WHERE s.tid=t.id AND t.id=#{tid}
</select>
<resultMap id="TeacherStudent2" type="com.mybatis.entity.Teacher">
<id property="id" column="tid"></id>
<result property="name" column="tname"></result>
<collection property="studentList" javaType="ArrayList" ofType="com.mybatis.entity.Student">
<result property="id" column="sid"></result>
<result property="name" column="sname"></result>
</collection>
</resultMap>
</mapper>