1. 概述
MyBatis 是一款持久层框架, 免除了几乎所有的 JDBC 代码工作。MyBatis通过 XML 或注解来配置和映射原始类型为数据库中的记录。
(以前叫ibatis, 2010年6月16号被谷歌托管,改名为MyBatis)
官方文档地址:https://mybatis.org/mybatis-3/zh/index.html
maven依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
2. 环境准备及测试样例
2.1 数据库准备
mysql,新建mybatis数据库,然后创建student表,创建三行数据。
CREATE SCHEMA `mybatis` DEFAULT CHARACTER SET utf8 COLLATE utf8_bin ;
CREATE TABLE `student` (
`id` int(11) NOT NULL COMMENT '学号',
`name` varchar(45) COLLATE utf8_bin DEFAULT NULL COMMENT '姓名',
`gender` varchar(45) COLLATE utf8_bin DEFAULT NULL COMMENT '性别',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
insert into student(`id`,`name`,`gender`) values
(1,'虞姬','女'),
(2,'鲁班七号','男'),
(3,'孙悟空','男');
2.2 idea新建maven工程,引入依赖
<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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.nesc</groupId>
<artifactId>mybaties01</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<!--junit测试依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
</dependencies>
</project>
2.3 创建mybatis-config.xml
官方文档地址上有模板,copy一个过来,官方推荐名叫mybatis-config.xml,不叫这个也行,就别费事自己瞎起名了。把自己数据配置写上,最下面那个标签先注释掉,文件默认放在resource根目录下
<configuration>
<!--可以存在多个数据库配置,default 只能选择其中一个id进行启用-->
<environments default="development">
<!--id 让上面选择启用谁的唯一标志-->
<environment id="development">
<!--Mybatis事务管理类型[JDBC|MANAGED]二选一,默认用的jdbc 那个没啥用 -->
<transactionManager type="JDBC"/>
<!--[UNPOOLED|POOLED|JNDI] 选POOLED 那俩没啥用-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--下面这句先注释-->
<!-- <mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>-->
</configuration>
2.4 编写mybatis工具类
package com.nesc.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;
/**
* @author wanghl
* @date 2020/11/30 9:12
**/
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static{
try {
//加载文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取SqlSession 公共方法
* @return
*/
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
2.5 编写数据库对应java实体类
package com.nesc.domain;
/**
* @author wanghl
* @date 2020/11/30 9:49
**/
public class Student {
/**
* 学号
*/
private int id;
/**
* 姓名
*/
private String name;
/**
* 性别
*/
private String gender;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", gender='" + gender + '\'' +
'}';
}
}
2.6 编写StudentDao
package com.nesc.dao;
import com.nesc.domain.Student;
import java.util.List;
/**
* @author wanghl
* @date 2020/11/30 10:03
**/
public interface StudentDao {
public List<Student> getAll();
}
2.7 编写mapper.xml
从官网copy个 mapper的xml文件 如下
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
改成自己的,resource文件目录下新建mapper文件夹,放在那里
<!--唯一命名空间,同时绑定dao接口 等同于实现接口-->
<mapper namespace="com.nesc.dao.StudentDao">
<!--id 代表要实现的接口方法 resultType 返回值类型-->
<select id="getAll" resultType="com.nesc.domain.Student">
select * from student
</select>
</mapper>
2.8 注册mapper
在mybatis-config.xml中注册自己的mapper,就是之前注释的那段,改成自己的
<!--写完代码 将mapper文件注册到 管理器中-->
<mappers>
<mapper resource="mapper/StudentMapper.xml"/>
</mappers>
2.9 编写测试类
package com.nesc.dao;
import com.nesc.domain.Student;
import com.nesc.util.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
/**
* @author wanghl
* @date 2020/11/30 10:12
**/
public class StudentDaoTest {
/**
* 测试 StudentDao的 getAll方法
*/
public void getAllTest(){
//从工具类种获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
try {
//获取mapper 接口对象
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
//调用 接口中 getAll 方法
List<Student> all = mapper.getAll();
//遍历结果
for (int i = 0; i < all.size(); i++) {
System.out.println(all.get(i).toString());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
}
}
2.10 运行结果
Student{id=1, name='虞姬', gender='女'}
Student{id=2, name='鲁班七号', gender='男'}
Student{id=3, name='孙悟空', gender='男'}
3. 增删改查实现
3.1 insert
StudentDao类新增接口定义
/**
* 新增学生数据
* @param student
* @return
*/
int insert(Student student);
StudentMapper.xml新增方法实现,参数用#{}接收方法定义中的参数名称值
<!--id 代表要实现的接口方法 parameterType 类型为对象的时候 下面参数会自动拆箱-->
<insert id="insert" parameterType="com.nesc.domain.Student">
insert into student(id ,name ,gender) values (#{id},#{name},#{gender})
</insert>
StudentDaoTest新增对insert的测试方法
/**
* 测试 StudentDao的 insert
*/
public void insert(){
//从工具类种获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
try {
//获取mapper 接口对象
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
Student student = new Student();
student.setId(4);
student.setName("蔡文姬");
student.setGender("女");
//调用 接口方法
int result = mapper.insert(student);
System.out.println(result);
//提交事务 否则执行不生效
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
}
第一次执行结果
1
第二次执行结果
Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '4' for key 'PRIMARY'
3.2 delete
StudentDao类新增接口定义
/**
* 根据id删除数据
* @param id
* @return
*/
int deleteById(int id);
StudentMapper.xml新增方法实现,参数用#{}接收方法定义中的参数名称值
<!--id 代表要实现的接口方法-->
<delete id="deleteById" parameterType="int">
delete from student where id = #{id}
</delete>
StudentDaoTest新增对deleteById的测试方法
/**
* 测试 StudentDao的 deleteById
*/
public void deleteByIdTest(){
//从工具类种获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
try {
//获取mapper 接口对象
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
//调用 接口方法
int result = mapper.deleteById(1);
System.out.println(result);
//提交事务 否则执行不生效
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
}
第一次执行结果:成功
1
第二次执行结果:失败
0
3.3 update
StudentDao类新增接口定义
/**
* 根据id修改数据
* @param student
* @return
*/
int updateById(Student student);
StudentMapper.xml新增方法实现,参数用#{}接收方法定义中的参数名称值
<!--id 代表要实现的接口方法 parameterType 类型为对象的时候 下面参数会自动拆箱-->
<update id="updateById" parameterType="com.nesc.domain.Student">
update student set name = #{name}, gender = #{gender} where id = #{id}
</update>
StudentDaoTest新增对updateById的测试方法
/**
* 测试 StudentDao的 updateById
*/
public void updateByIdTest(){
//从工具类种获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
try {
//获取mapper 接口对象
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
Student student = new Student();
student.setId(2);
student.setName("李信");
student.setGender("男");
//调用 接口方法
int result = mapper.updateById(student);
System.out.println(result);
Student s = mapper.getById(2);
System.out.println(s);
//提交事务 否则执行不生效
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
}
执行结果:成功
1
Student{id=2, name='李信', gender='男'}
3.4 select
StudentDao类新增查询接口定义
/**
* 根据id查询数据
* @param id
* @return
*/
Student getById(int id);
StudentMapper.xml新增方法实现,参数用#{}接收方法定义中的参数名称值
<!--id 代表要实现的接口方法 resultType 返回值类型 parameterType 参数类型-->
<select id="getById" parameterType="int" resultType="com.nesc.domain.Student">
select * from student where id = #{id}
</select>
StudentDaoTest新增对getById的测试方法
/**
* 测试 StudentDao的 getById
*/
public void getByIdTest(){
//从工具类种获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
try {
//获取mapper 接口对象
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
//调用 接口方法
Student student = mapper.getById(1);
System.out.println(student.toString());
} catch (Exception e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
}
执行结果
Student{id=1, name='虞姬', gender='女'}
4. Mybatis 配置文件(mybatis-config.xml)
从xml提示中可以看见,标签中一共11个字标签,且有先后顺序,顺序写错,提示器会给错误提示,并给出实际顺序提示。
(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)
从官方文档看介绍了9个。
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
4.1 属性(propertie)
实战常用的是将数据库相关信息存到db.properties文件中,然后将文件引入到mybatis-config.xml配置文件中,在文件中通过${}对变量进行绑定,
4.1.1 常见例子
新建db.properties文件,放在resource目录下
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC
username=root
password=123456
mybatis-config.xml, 重点看引入、数据源配置的使用
<configuration>
<!--引入同目录下的db.properties文件-->
<properties resource="db.properties"></properties>
<!--可以存在多个数据库配置,default 只能选择其中一个id进行启用-->
<environments default="development">
<!--id 让上面选择启用谁的唯一标志-->
<environment id="development">
<!--Mybatis事务管理类型[JDBC|MANAGED]二选一,默认用的jdbc 那个没啥用 -->
<transactionManager type="JDBC"/>
<!--[UNPOOLED|POOLED|JNDI] 选POOLED 那俩没啥用-->
<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>
<!--写完代码 将mapper文件注册到 管理器中-->
<mappers>
<mapper resource="mapper/StudentMapper.xml"/>
</mappers>
</configuration>
4.1.2 扩展
如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:
- 首先读取在 properties 元素体内指定的属性。
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件。
- 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。
例子如下
<!--db.properties文件中的内容会覆盖 此处变量-->
<properties resource="db.properties">
<property name="username" value="wanghl12W"></property>
</properties>
//初始化传入参数 会覆盖db.properties的变量值
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);
// ... 或者 ...
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props);
4.2 类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
换句话说就是非必要,有他代码看起来简短一些,没他代码长一些,没有也行。
4.2.1 常规例子
在mybatis-config.xml中配置
<typeAliases>
<!--给com.nesc.domain.Student 起个 唯一的小名-->
<typeAlias type="com.nesc.domain.Student" alias="student"></typeAlias>
</typeAliases>
然后在StudentMapper.xml中就可以这样写,把里面所有的com.nesc.domain.Student 都替换成student,这里主要是resultType使用了
<!--唯一命名空间,同时绑定dao接口 等同于实现接口-->
<mapper namespace="com.nesc.dao.StudentDao">
<!--id 代表要实现的接口方法 resultType 返回值类型-->
<select id="getAll" resultType="student">
select * from student
</select>
<!--id 代表要实现的接口方法 resultType 返回值类型 parameterType 参数类型-->
<select id="getById" parameterType="int" resultType="student">
select * from student where id = #{id}
</select>
<!--id 代表要实现的接口方法-->
<delete id="deleteById" parameterType="student">
delete from student where id = #{id}
</delete>
<!--id 代表要实现的接口方法 parameterType 类型为对象的时候 下面参数会自动拆箱-->
<insert id="insert" parameterType="student">
insert into student(id ,name ,gender) values (#{id},#{name},#{gender})
</insert>
<!--id 代表要实现的接口方法 parameterType 类型为对象的时候 下面参数会自动拆箱-->
<update id="updateById" parameterType="student">
update student set name = #{name}, gender = #{gender} where id = #{id}
</update>
</mapper>
4.2.2 实战常用方式
为了防止配置文件中写过多的标签,显着配置文件长,可以配置包名+@Alias标签实现,同时也减少对配置文件的频繁更改
在mybatis-config.xml中配置
<!--扫描com.nesc.domain包下,所有类名头上有@Alias("xxx")标签的自动注册到这里 没有写Alias的 默认将类名首字母小写 拿过来进行注册-->
<typeAliases>
<package name="com.nesc.domain"/>
</typeAliases>
在com.nesc.domain.Student类名头上配置
package com.nesc.domain;
import org.apache.ibatis.type.Alias;
("student")
public class Student {
//...
}
4.2.3 扩展(了解)
下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
别名 | 映射的类型 |
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
4.3 设置(setting)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。全部参数看官方文档,此处记录常用的
<settings>
<!--全局性地开启或关闭所有映射器配置文件中已配置的任何缓存-->
<setting name="cacheEnabled" value="true"/>
<!--允许 JDBC 支持自动生成主键,需要数据库驱动支持-->
<setting name="useGeneratedKeys" value="false"/>
<!--是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn-->
<setting name="mapUnderscoreToCamelCase" value="false"/>
<!--开启mybatis日志,不同日志可能需要引用第三方日志依赖-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
4.4 映射器(mappers)
四种方式绑定注册*Mapper.xml,
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器,此方法xml文件与java文件必须同目录,必须同名 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
放在类中打包时候src/main/java下的xml默认不会打包编译,需要在pom中增加编译配置
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
4.5 数据库厂商标识(databaseIdProvider)
兼容多数据库,有时候开发的时候不确定数据库版本,写sql语句的时候不同版本数据的情况就都写出来
mybatis-config.xml中增加如下配置
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mySQL"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
然后在*Mapper.xml中用法 用databaseId变量来判断,优先找到匹配的
<select id="selectTime" resultType="String" databaseId="mySQL">
SELECT NOW()
</select>
<select id="selectTime" resultType="String" databaseId="oracle">
SELECT 'oralce'||to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') FROM dual
</select>
或是if中用_databaseId变量判断
<select id="selectTime" resultType="String">
<if test="_databaseId == 'oracle'">
select seq_users.nextval from dual
</if>
<if test="_databaseId == 'mySQL'">
select now()
</if>
</select>
如果不知道自己数据库名称,用下面代码执行一下看看
/**
* 获取数据库类型
*/
public void getDbTypeTest() {
//从工具类种获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//java.sql.Connection
Connection connection = null;
try {
connection = sqlSession.getConnection();
String dbName = connection.getMetaData().getDatabaseProductName();
String dbVersion = connection.getMetaData().getDatabaseProductVersion();
System.out.println("数据库名称是:" + dbName + ";版本是:" + dbVersion);
} catch (SQLException e) {
e.printStackTrace();
}
}
5. 结果映射(resultMap)
resultMap
元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets
数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。
这里提一下resultType,实际上resultType是后台默认创建了一个resultMap去将查询结果的column和java bean的properties关联起来,如果这俩名称不一致的时候,这时候就需要显式的将resultMap写出来,比如数据库字段叫sex,而java bean中类属性叫gender,这就不匹配,有查询结果,但是接收不到。
5.1 简单映射
解决一些字段属性不一致问题,这里我们把数据库表student中gender字段改名为sex
ALTER TABLE `mybatis`.`student`
CHANGE COLUMN `gender` `sex` VARCHAR(45) NULL DEFAULT NULL COMMENT '性别' ;
StudentMapper.xml
<!--唯一命名空间,同时绑定dao接口 等同于实现接口-->
<mapper namespace="com.nesc.dao.StudentDao">
<!--这里自定义结果映射,不一致的属性在下面进行配置,一致的默认不用动-->
<resultMap id="studentResult" type="student">
<result column="sex" property="gender"></result>
</resultMap>
<!--这里用自定义resultMap 代替 resultType -->
<select id="getAll" resultMap="studentResult">
select id,name,sex from student
</select>
</mapper>
5.2 复杂映射
5.2.1 一对一(association)
一对一,代码表现形式就是java类中包含另一个类对象
新建国家系列代码
- 新建表、插入数据
CREATE TABLE `mybatis`.`country` (
`id` INT NOT NULL,
`name` VARCHAR(45) NULL,
`child_id` INT NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_bin;
INSERT INTO `mybatis`.`country` (`id`, `name`, `child_id`) VALUES ('1', '大秦', '34', '2');
INSERT INTO `mybatis`.`country` (`id`, `name`, `child_id`) VALUES ('2', '东胜神洲', '25', '3');
INSERT INTO `mybatis`.`country` (`id`, `name`, `child_id`) VALUES ('3', '东汉', '28', '4');
- 新增Country.java
package com.nesc.domain;
import org.apache.ibatis.type.Alias;
/**
* @author wanghl
* @date 2020/11/30 9:49
**/
("country")
public class Country {
/**
* 国家id
*/
private int id;
/**
* 姓名
*/
private String name;
/**
* 子民ID
*/
private int childId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getChildId() {
return childId;
}
public void setChildId(int childId) {
this.childId = childId;
}
public String toString() {
return "Country{" +
"id=" + id +
", name='" + name + '\'' +
", childId=" + childId +
'}';
}
}
- Student类中 新增Country属性
package com.nesc.domain;
import org.apache.ibatis.type.Alias;
/**
* @author wanghl
* @date 2020/11/30 9:49
**/
("student")
public class Student {
/**
* 学号
*/
private int id;
/**
* 姓名
*/
private String name;
/**
* 性别
*/
private String gender;
/**
* 国家
*/
private Country country;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Country getCountry() {
return country;
}
public void setCountry(Country country) {
this.country = country;
}
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", gender='" + gender + '\'' +
", country=" + country +
'}';
}
}
- StudentMapper.xml 修改getAll方法及对应resultMap
<!--id 代表要实现的接口方法 resultType 返回值类型-->
<select id="getAll" resultMap="studentResult">
select
s.id,
s.name,
s.sex,
c.id country_id,
c.name country_name,
c.child_id
from student s ,country c
where s.id = c.child_id
</select>
<!--这里自定义结果映射,复杂关系映射全部属性都要写-->
<resultMap id="studentResult" type="student">
<!--这里写id标签会快一些,result标签也不是不行-->
<id property="id" column="id"></id>
<result property="name" column="name" ></result>
<result property="gender" column="sex"></result>
<!--一对一对象就用association标签,property表示在student类中Country类的变量名,
javaType就是完整的Country类型 可以用别名country,这里这么写直观一些-->
<association property="country" javaType="com.nesc.domain.Country" >
<id property="id" column="country_id"></id>
<result property="name" column="country_name"></result>
<result property="childId" column="child_id"></result>
</association>
</resultMap>
- 运行StudentDaoTest中getAllTest后运行结果
Student{id=2, name='李信', gender='男', country=Country{id=1, name='大秦', childId=2}}
Student{id=3, name='孙悟空', gender='男', country=Country{id=2, name='东胜神洲', childId=3}}
Student{id=4, name='蔡文姬', gender='女', country=Country{id=3, name='东汉', childId=4}}
5.2.2 一对多(collection)
新建装备库一系列代码
- 新建表及数据
CREATE TABLE `mybatis`.`armory` (
`id` INT NOT NULL,
`name` VARCHAR(45) NULL,
`student_id` INT NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_bin;
INSERT INTO `mybatis`.`armory` (`id`, `name`, `student_id`) VALUES ('1', '暗影战斧', '2');
INSERT INTO `mybatis`.`armory` (`id`, `name`, `student_id`) VALUES ('2', '名刀', '2');
INSERT INTO `mybatis`.`armory` (`id`, `name`, `student_id`) VALUES ('3', '破军', '2');
INSERT INTO `mybatis`.`armory` (`id`, `name`, `student_id`) VALUES ('4', '追猎刀锋', '3');
INSERT INTO `mybatis`.`armory` (`id`, `name`, `student_id`) VALUES ('5', '无尽战刃', '3');
INSERT INTO `mybatis`.`armory` (`id`, `name`, `student_id`) VALUES ('6', '宗师之力', '3');
INSERT INTO `mybatis`.`armory` (`id`, `name`, `student_id`) VALUES ('7', '圣杯', '4');
INSERT INTO `mybatis`.`armory` (`id`, `name`, `student_id`) VALUES ('8', '极影', '4');
INSERT INTO `mybatis`.`armory` (`id`, `name`, `student_id`) VALUES ('9', '冷静之靴', '4');
- 新建Armory类
package com.nesc.domain;
import org.apache.ibatis.type.Alias;
/**
* @author wanghl
* @date 2020/12/2 9:57
**/
("armory")
public class Armory {
/**
* 装备id
*/
private int id;
/**
* 装备名称
*/
private String name;
/**
* 所属学生
*/
private int studentId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getStudentId() {
return studentId;
}
public void setStudentId(int studentId) {
this.studentId = studentId;
}
public String toString() {
return "Armory{" +
"id=" + id +
", name='" + name + '\'' +
", studentId=" + studentId +
'}';
}
}
- Student类新增List属性
package com.nesc.domain;
import org.apache.ibatis.type.Alias;
import java.util.List;
/**
* @author wanghl
* @date 2020/11/30 9:49
**/
("student")
public class Student {
/**
* 学号
*/
private int id;
/**
* 姓名
*/
private String name;
/**
* 性别
*/
private String gender;
/**
* 国家
*/
private Country country;
/**
* 装备
*/
private List<Armory> list;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Country getCountry() {
return country;
}
public void setCountry(Country country) {
this.country = country;
}
public List<Armory> getList() {
return list;
}
public void setList(List<Armory> list) {
this.list = list;
}
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", gender='" + gender + '\'' +
", country=" + country +
", list=" + list +
'}';
}
}
- StudentMapper.xml 修改getAll方法及对应resultMap
<!--这里自定义结果映射,复杂关系映射全部属性都要写-->
<resultMap id="studentResult" type="student">
<!--这里写id标签会快一些,result标签也不是不行-->
<id property="id" column="id"></id>
<result property="name" column="name"></result>
<result property="gender" column="sex"></result>
<!--一对一对象就用association标签,property表示在student类中Country类的变量名,
javaType就是完整的Country类型 可以用别名country,这里这么写直观一些-->
<association property="country" javaType="com.nesc.domain.Country" >
<id property="id" column="country_id"></id>
<result property="name" column="country_name"></result>
<result property="childId" column="child_id"></result>
</association>
<!--一对一对象就用collection标签,property表示在student类中Armory类的变量名,
ofType就是完整的Country类型 可以用别名country,这里这么写直观一些-->
<collection property="list" ofType="com.nesc.domain.Armory">
<id property="id" column="arms_id"></id>
<result property="name" column="arms_name"></result>
<result property="studentId" column="student_id"></result>
</collection>
</resultMap>
<!--id 代表要实现的接口方法 resultType 返回值类型-->
<select id="getAll" resultMap="studentResult">
select
s.id,
s.name,
s.sex,
c.id country_id,
c.name country_name,
c.child_id,
a.id arms_id,
a.name arms_name,
a.student_id
from student s ,country c,armory a
where s.id = c.child_id and s.id = a.student_id
</select>
- 运行StudentDaoTest中getAllTest后运行结果
Student{id=2, name='李信', gender='男', country=Country{id=1, name='大秦', childId=2}, list=[Armory{id=1, name='暗影战斧', studentId=2}, Armory{id=2, name='名刀', studentId=2}, Armory{id=3, name='破军', studentId=2}]}
Student{id=3, name='孙悟空', gender='男', country=Country{id=2, name='东胜神洲', childId=3}, list=[Armory{id=4, name='追猎刀锋', studentId=3}, Armory{id=5, name='无尽战刃', studentId=3}, Armory{id=6, name='宗师之力', studentId=3}]}
Student{id=4, name='蔡文姬', gender='女', country=Country{id=3, name='东汉', childId=4}, list=[Armory{id=7, name='圣杯', studentId=4}, Armory{id=8, name='极影', studentId=4}, Armory{id=9, name='冷静之靴', studentId=4}]}
5.2.3 多对一
就是一对多主体换一下,sql都差不多,就是接收的resultMap不一样,主体对换了
- Armory类中增加Student属性
package com.nesc.domain;
import org.apache.ibatis.type.Alias;
/**
* @author wanghl
* @date 2020/12/2 9:57
**/
("armory")
public class Armory {
/**
* 装备id
*/
private int id;
/**
* 装备名称
*/
private String name;
/**
* 所属学生
*/
private int studentId;
private Student student;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getStudentId() {
return studentId;
}
public void setStudentId(int studentId) {
this.studentId = studentId;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public String toString() {
return "Armory{" +
"id=" + id +
", name='" + name + '\'' +
", studentId=" + studentId +
", student=" + student +
'}';
}
}
- 新增ArmoryDao.java及ArmoryMapper.xml
package com.nesc.dao;
import com.nesc.domain.Armory;
import com.nesc.domain.Student;
import java.util.List;
/**
* @author wanghl
* @date 2020/12/2 10:03
**/
public interface ArmoryDao {
/**
* 查询所有数据
* @return
*/
List<Armory> getAll();
}
ArmoryMapper.xml
<!--唯一命名空间,同时绑定dao接口 等同于实现接口-->
<mapper namespace="com.nesc.dao.ArmoryDao">
<!--这里自定义结果映射,复杂关系映射全部属性都要写-->
<resultMap id="armoryResult" type="armory">
<!--这里写id标签会快一些,result标签也不是不行-->
<id property="id" column="arms_id"></id>
<result property="name" column="arms_name"></result>
<result property="studentId" column="student_id" ></result>
<collection property="student" ofType="com.nesc.domain.Student">
<id property="id" column="arms_id"></id>
<result property="name" column="arms_name"></result>
<result property="gender" column="sex"></result>
</collection>
</resultMap>
<!--id 代表要实现的接口方法 resultType 返回值类型-->
<select id="getAll" resultMap="armoryResult">
select
s.id,
s.name,
s.sex,
a.id arms_id,
a.name arms_name,
a.student_id
from student s ,armory a
where s.id = a.student_id
</select>
</mapper>
- mybatis-config.xml注册mapper
<mappers>
<mapper resource="mapper/StudentMapper.xml"/>
<mapper resource="mapper/ArmoryMapper.xml"/>
</mappers>
- 新建ArmoryDaoTest测试类
package com.nesc.dao;
import com.nesc.domain.Armory;
import com.nesc.domain.Student;
import com.nesc.util.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
/**
* @author wanghl
* @date 2020/12/2 10:12
**/
public class ArmoryDaoTest {
/**
* 测试 ArmoryDao getAll方法
*/
public void getAllTest(){
//从工具类种获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
try {
//获取mapper 接口对象
ArmoryDao mapper = sqlSession.getMapper(ArmoryDao.class);
//调用 接口中 getAll 方法
List<Armory> all = mapper.getAll();
//遍历结果
for (int i = 0; i < all.size(); i++) {
System.out.println(all.get(i).toString());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
}
}
- 运行getAll测试方法
Armory{id=1, name='暗影战斧', studentId=2, student=Student{id=1, name='暗影战斧', gender='男', country=null, list=null}}
Armory{id=2, name='名刀', studentId=2, student=Student{id=2, name='名刀', gender='男', country=null, list=null}}
Armory{id=3, name='破军', studentId=2, student=Student{id=3, name='破军', gender='男', country=null, list=null}}
Armory{id=4, name='追猎刀锋', studentId=3, student=Student{id=4, name='追猎刀锋', gender='男', country=null, list=null}}
Armory{id=5, name='无尽战刃', studentId=3, student=Student{id=5, name='无尽战刃', gender='男', country=null, list=null}}
Armory{id=6, name='宗师之力', studentId=3, student=Student{id=6, name='宗师之力', gender='男', country=null, list=null}}
Armory{id=7, name='圣杯', studentId=4, student=Student{id=7, name='圣杯', gender='女', country=null, list=null}}
Armory{id=8, name='极影', studentId=4, student=Student{id=8, name='极影', gender='女', country=null, list=null}}
Armory{id=9, name='冷静之靴', studentId=4, student=Student{id=9, name='冷静之靴', gender='女', country=null, list=null}}
5.2.4 多对多
同一对多,只不过是双方都是一对多关系而已,写法同一对多没区别
6. 动态SQL
官方:动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
总结:没有也能对付用,用了能省不少事。
6.1 if
常用在表单查询页面多个查询条件,N选几的情况,动态拼接SQL
好处:
- 如果id不为空,不会出现 SELECT * FROM student WHERE AND id = #{id} 的情况,and会被过滤
- 前端页面多条件N选2查询会动态拼接sql
<select id="findStudent" resultType="student">
SELECT * FROM student WHERE sex = '男'
<if test="id != null">
AND id = #{id}
</if>
<if test="name != null">
AND name = #{name}
</if>
</select>
6.2 choose、when、otherwise
mybatis版本的switch case
<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>
6.3 trim、where、set
先说where,标签 就是代替where的,好处是 当where后续条件不满足条件没拼接的时候不会出现 select 1 from dual where 这种半截sql语句,而且如果where后面是个 and 也会给过滤掉
<select id="findStudent" resultType="student">
SELECT * FROM student
<where>
<if test="id != null">
AND id = #{id}
</if>
<if test="name != null">
AND name = #{name}
</if>
</where>
</select>
set标签,就是动态的update,也会过滤set属性后多余的逗号
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
再说trim标签,上面where会过滤and,set会过滤逗号,都是trim实现的,这个可以对其他标签自定义
where过滤原理
<trim prefix="WHERE" prefixOverrides="AND">
...
</trim>
set过滤原理
<trim prefix="SET" suffixOverrides=",">
...
</trim>
6.4 foreach
主要用在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>
7. 缓存
减少和数据库交互次数,减少系统开销,提升效率。mybatis默认分一级缓存和二级缓存
7.1 一级缓存
也叫本地缓存,SqlSession级别的,也就是SqlSession开关一次 就失效了。当sqlSession执行增删改的时候,就会清空一级缓存
一级缓存特点
- 和二级缓存不同它不需要配置.默认开启
- 不得不用,无法剔除,mybatis-config.xml中 setting配置的 cacheEnabled=false 无法关闭一级缓存
- 无法管理一级缓存
为了演示效果,需要配置mybatis-config.xml的log来查看调用数据库情况,这里用STDOUT_LOGGING,这个不用额外引入依赖
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
代码示例
/**
* 测试一级缓存(本地缓存)
* 通过下面示例的执行日志可知,第一次调用数据库中访问,有sql打印,第二次从内存直接读取,执行更新/删除语句后,缓存清空,第三次再次调用数据库,打印sql语句
*/
public void localSessionCacheTest(){
//从工具类种获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
try {
//获取mapper 接口对象
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
System.out.println("=============第一次调用==============");
mapper.getById(2);
System.out.println("=============第二次调用==============");
mapper.getById(2);
System.out.println("=============任意更新/删除语句==============");
//我删除id=3的数据 任何删除更新语句都可以 本次缓存清空
mapper.deleteById(3);
sqlSession.commit();
System.out.println("=============第三次调用==============");
mapper.getById(2);
} catch (Exception e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
}
执行日志
Created connection 1691875296.
Returned connection 1691875296 to pool.
=============第一次调用==============
Opening JDBC Connection
Checked out connection 1691875296 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@64d7f7e0]
==> Preparing: select * from student where id = ?
==> Parameters: 2(Integer)
<== Columns: id, name, sex
<== Row: 2, 李信, 男
<== Total: 1
=============第二次调用==============
=============任意更新/删除语句==============
==> Preparing: delete from student where id = ?
==> Parameters: 3(Integer)
<== Updates: 1
Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@64d7f7e0]
=============第三次调用==============
==> Preparing: select * from student where id = ?
==> Parameters: 2(Integer)
<== Columns: id, name, sex
<== Row: 2, 李信, 男
<== Total: 1
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@64d7f7e0]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@64d7f7e0]
7.2 二级缓存
也叫本地全局缓存,namespace级别的(*Mapper.xml里那个),随应用重启后失效
二级缓存开启
- mybatis-config.xml中 setting配置的 cacheEnabled=true ,虽说默认不用配置,但是不能给改成false,这个可以关闭二级缓存
- *Mapper.xml中增加标签
<!--
eviction 缓存策略
flushInterval 缓存刷新时间 ms单位
size 最大缓存引用个数 达到上限根据缓存策略进行处理
readOnly 返回的对象被认为是只读的 修改可能会在不同线程中的调用者产生冲突
-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
可用的清除策略有:
-
LRU
– 最近最少使用:移除最长时间不被使用的对象。 -
FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。 -
SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。 -
WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU。
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
代码示例
StudentMapper.xml中增加cache
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
java测试用例
/**
* 测试二级缓存(全局缓存)
* 通过下面示例和执行结果可知,第一次读数据库,第二次缓存,第三次数据库,第四次数据库
*/
public void SecondLevelSessionCacheTest() {
try {
System.out.println("=============第一次调用==============");
//从工具类种获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//获取mapper 接口对象
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
//调用 接口中 getAll 方法
mapper.getById(2);
sqlSession.close();
System.out.println("=============第二次调用==============");
sqlSession = MybatisUtils.getSqlSession();
//获取mapper 接口对象
mapper = sqlSession.getMapper(StudentDao.class);
//调用 接口中 getAll 方法
mapper.getById(2);
sqlSession.close();
System.out.println("=============任意更新/删除语句==============");
sqlSession = MybatisUtils.getSqlSession();
//获取mapper 接口对象
mapper = sqlSession.getMapper(StudentDao.class);
//我删除id= 3的数据
mapper.deleteById(3);
sqlSession.commit();
sqlSession.close();
System.out.println("=============第三次调用==============");
sqlSession = MybatisUtils.getSqlSession();
//获取mapper 接口对象
mapper = sqlSession.getMapper(StudentDao.class);
mapper.getById(2);
sqlSession.close();
} catch (Exception e) {
e.printStackTrace();
}
}
执行结果
Created connection 1691875296.
Returned connection 1691875296 to pool.
Cache Hit Ratio [com.nesc.dao.StudentDao]: 0.0
Opening JDBC Connection
Checked out connection 1691875296 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@64d7f7e0]
==> Preparing: select * from student where id = ?
==> Parameters: 2(Integer)
<== Columns: id, name, sex
<== Row: 2, 李信, 男
<== Total: 1
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@64d7f7e0]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@64d7f7e0]
Returned connection 1691875296 to pool.
=============第二次调用==============
Cache Hit Ratio [com.nesc.dao.StudentDao]: 0.5
=============任意更新/删除语句==============
Opening JDBC Connection
Checked out connection 1691875296 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@64d7f7e0]
==> Preparing: delete from student where id = ?
==> Parameters: 3(Integer)
<== Updates: 0
Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@64d7f7e0]
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@64d7f7e0]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@64d7f7e0]
Returned connection 1691875296 to pool.
=============第三次调用==============
Cache Hit Ratio [com.nesc.dao.StudentDao]: 0.3333333333333333
Opening JDBC Connection
Checked out connection 1691875296 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@64d7f7e0]
==> Preparing: select * from student where id = ?
==> Parameters: 2(Integer)
<== Columns: id, name, sex
<== Row: 2, 李信, 男
<== Total: 1
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@64d7f7e0]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@64d7f7e0]
Returned connection 1691875296 to pool.