更多详细配置可参考:MyBatis官方文档
MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于 2013年11月迁移到Github。
官方文档的描述:
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
总的来说MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架
下载地址:https://github.com/mybatis/mybatis-3
使用idea创建一个maven工程
引入相关依赖(这里使用MyBatis3.5.7)
<dependencies>
<!-- Mybatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.3</version>
</dependency>
</dependencies>
创建并编写配置文件
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
- 1
- 2
- 3
XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。
配置文件示例:
配置文件名称可以自定义,但是建议使用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="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`password` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`age` int(11) NULL DEFAULT NULL,
`email` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
public class User {
private Integer id;
private String username;
private String password;
private String sex;
private Integer age;
private String email;
constructor...
set and get ...
}
Mapper接口相当于原来的Dao,但是不需要我们手动编写其实现类。但是需要配置对应的xml文件,这是因为MyBatis在底层通过反射读取xml文件在程序运行时动态为我们生成了实现类。
public interface UserMapper {
int insertUser(User user);
}
1、映射文件命名规则
表所对应的实体类的类名+Mapper.xml,例如:UserMapper.java -> UserMapper.xml
2、Mapper文件作用
用于编写SQL语句,实现对数据库的操作。
3、MyBatis中可以面向接口操作数据,要保证两个一致
- mapper接口的全类名和映射文件的命名空间(namespace)保持一致
- mapper接口中方法的方法名和映射文件中编写SQL的标签的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">
<mapper namespace="com.jc.mybatis.mapper.UserMapper">
<insert id="insertUser">
insert into t_user values(null, "admin", "123123", "男", 18, "2307491719@qq.com")
</insert>
</mapper>
引入依赖
<!--引入日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
在resources文件夹下添加log4j.properties文件
# resources文件夹根目录下
### 配置根
log4j.rootLogger = debug,console
### 日志输出到控制台显示
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
创建测试类:
import com.jc.mybatis.mapper.UserMapper;
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 org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
public class MyBatisTest {
@Test
public void testInsertUser() throws IOException {
// 获取输入流
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
// 创建sqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 创建SqlSession工厂
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
// 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取Mapper对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int result = userMapper.insertUser();
// 因为默认的事务管理器时JDBC,所以需要手动提交事务
sqlSession.commit();
System.out.println("result = " + result);
}
}
输出:
result = 1
要注意以下标签在MyBatis配置文件中是有配置顺序的。
配置顺序如下
1. properties
2. settings
3. typeAliases
4. typeHandlers
5. objectFactory
6. objectWrapperFactory
7. reflectorFactory
8. plugins?,environments
9. databaseIdProvider
10. mappers
<!--
environments:设置多个连接数据库的环境
default:默认使用某个数据库环境
-->
<environments default="development">
<!--
environment:设置具体的连接数据库的环境信息
属性:
id:设置环境的唯一标识,可通过environments标签中的default设置某一个环境的id,表示默认使用的环境
-->
<environment id="development">
<!--
transactionManager:设置事务管理方式
属性:
type:设置事务管理方式,type="JDBC|MANAGED"
type="JDBC":设置当前环境的事务管理都必须手动处理
type="MANAGED":设置事务被管理,例如spring中的AOP
-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
用于引入Properties文件。
使用举例:
我们将数据库配置信息配置到properties文件中,通过properties标签引入。
<properties resource="jdbc.properties"/>
jdbc.properties配置文件:
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF-8
jdbc.username = root
jdbc.password = 123456
用于配置mapper文件中类型别名。
type:要指定别名的类型的全类名
alias: 别名,如果不指定alias的值,默认是类名,并且不区分大小写
<typeAliases>
<typeAlias type="com.jc.mybatis.pojo.User" alias="User"/>
</typeAliases>
也可以以包为单位设置别名:这时的别名默认就是不区分大小写的类名
<typeAliases>
<package name="com.jc.mybatis.pojo"/>
</typeAliases>
mapper文件中可以这样写:
<select id="getAllUsers" resultType="user">
select * from t_user
</select>
用于引入mapper文件。
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
<mapper resource="mappers/XxxMapper.xml"/>
...
</mappers>
也可以以包为单位引入mapper文件:
但是要注意:此方式必须保证mapper接口和mapper映射文件必须在相同的包下
<mappers>
<package name="com.jc.mybatis.mapper"/>
</mappers>
${} 和 #{}#{} 的本质就是占位符取值,${} 的本质就是字符串拼接。${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号。先来复习一下JDBC中sql的使用操作:
@Test
public void testJDBC() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
String username = "Tom";
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF-8", "root", "123456");
PreparedStatement preparedStatement = conn.prepareStatement("select * from t_user where username = ?");
preparedStatement.setString(1, username);
}
这里是使用占位符的方式,也就是#{}。那么使用字符串拼接的方式就是${}。
下面来举例在MyBatis使用时参数的情况。
若mapper接口中的方法参数为单个的字面量类型
此时可以使用${}和#{}以任意的名称获取参数的值,注意${}需要手动加单引号
<select id="selectUserByUsername" resultType="com.jc.mybatis.pojo.User">
select * from t_user where username = #{username}
</select>
同样可以执行
<select id="selectUserByUsername" resultType="com.jc.mybatis.pojo.User">
select * from t_user where username = #{xxx}
</select>
使用${}需要加上单引号
<select id="selectUserByUsername" resultType="com.jc.mybatis.pojo.User">
select * from t_user where username = '${usename}'
</select>
同样可以执行
<select id="selectUserByUsername" resultType="com.jc.mybatis.pojo.User">
select * from t_user where username = '${yyy}'
</select>
若mapper接口中的方法参数为多个时此时MyBatis会自动将这些参数放在一个map集合中 ,以arg0,arg1…为键,以参数为值;以param1,param2…为键,以参数为值;因此只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号。
可以运行:
<select id="selectUserByUsernameAndPassword" resultType="com.jc.mybatis.pojo.User">
select * from t_user where username=#{param1} and password = #{param2}
</select>
<select id="selectUserByUsernameAndPassword" resultType="com.jc.mybatis.pojo.User">
select * from t_user where username='${param1}' and password = '${param2}'
</select>
报错:
<select id="selectUserByUsernameAndPassword" resultType="com.jc.mybatis.pojo.User">
select * from t_user where username='${username}' and password = '${password}'
</select>
若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在map中只需要通过 ${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号。
<select id="selectUserByMap" resultType="com.jc.mybatis.pojo.User">
select * from t_user where username=#{username} and password = #{password}
</select>
@Test
public void test3() {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map map = new HashMap<String, String>();
map.put("username", "Tom");
map.put("password", "123456");
User user = mapper.selectUserByMap(map);
System.out.println("user = " + user);
}
若mapper接口中的方法参数为实体类对象时此时可以使用${}和#{},通过访问实体类对象中的属性名获取属性值,注意${}需要手动加单引号。
<insert id="save">
insert into t_user values(null, #{username}, #{password}, #{sex}, #{age}, #{email})
</insert>
@Test
public void test4() {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setUsername("admin");
user.setPassword("888888");
user.setSex("男");
user.setAge(11);
user.setEmail("admin@gmail.com");
int save = mapper.save(user);
System.out.println("save = " + save);
}
可以通过@Param注解标识mapper接口中的方法参数。
此时,会将这些参数放在map集合中,以@Param注解的value属性值为键,以参数为值;以 param1,param2…为键,以参数为值;只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号。
User selectByUsername(@Param("username") String username);
<select id="selectByUsername" resultType="com.jc.mybatis.pojo.User">
select * from t_user where username = #{username}
</select>
/**
* 根据用户id查询用户信息
* @param id
* @return
*/
User getUserById(@Param("id") int id);
<!--User getUserById(@Param("id") int id);-->
<select id="getUserById" resultType="User">
select * from t_user where id = #{id}
</select>
/**
* 查询所有用户信息
* @return
*/
List<User> getUserList();
<!--List<User> getUserList();-->
<select id="getUserList" resultType="User">
select * from t_user
</select>
/**
* 查询用户的总记录数
* @return
* 在MyBatis中,对于Java中常用的类型都设置了类型别名
* 例如:java.lang.Integer-->int|integer
* 例如:int-->_int|_integer
* 例如:Map-->map,List-->list
*/
int getCount();
<!--int getCount();-->
<select id="getCount" resultType="_integer">
select count(id) from t_user
</select>
/**
* 根据用户id查询用户信息为map集合
* @param id
* @return
*/
Map<String, Object> getUserToMap(@Param("id") int id);
<!--Map<String, Object> getUserToMap(@Param("id") int id);-->
<select id="getUserToMap" resultType="map">
select * from t_user where id = #{id}
</select>
<!--结果:{password=123456, sex=男, id=1, age=23, username=admin}-->
方式一:
/**
* 查询所有用户信息为map集合
* @return
* 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,此时可以将这些map放在一个list集合中获取
*/
List<Map<String, Object>> getAllUserToMap();
<!--Map<String, Object> getAllUserToMap();-->
<select id="getAllUserToMap" resultType="map">
select * from t_user
</select>
方式二:
/**
* 查询所有用户信息为map集合
* @return
* 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,并
且最终要以一个map的方式返回数据,此时需要通过@MapKey注解设置map集合的键,值是每条数据所对应的
map集合
*/
@MapKey("id")
Map<String, Object> getAllUserToMap();
<!--Map<String, Object> getAllUserToMap();-->
<select id="getAllUserToMap" resultType="map">
select * from t_user
</select>
结果:
<!--
{
1={password=123456, sex=男, id=1, age=23, username=admin},
2={password=123456, sex=男, id=2, age=23, username=张三},
3={password=123456, sex=男, id=3, age=23, username=张三}
}
-->
/**
* 测试模糊查询
* @param mohu
* @return
*/
List<User> testMohu(@Param("mohu") String mohu);
<!--List<User> testMohu(@Param("mohu") String mohu);-->
<select id="testMohu" resultType="User">
<!--select * from t_user where username like '%${mohu}%'-->
<!--select * from t_user where username like concat('%',#{mohu},'%')-->
select * from t_user where username like "%"#{mohu}"%"
</select>
上面三种都可以使用,最后一种使用的最多,本质上是字符串拼接的问题。
/**
* 批量删除
* @param ids
* @return
*/
int deleteMore(@Param("ids") String ids);
<!--int deleteMore(@Param("ids") String ids);-->
<delete id="deleteMore">
delete from t_user where id in (${ids})
</delete>
只能使用${},不能使用#{},因为#{}会自动拼接单引号,sql语句就会变成这样:delete from t_user where id in ('1,2,3'),这样的SQL语句执行是没有效果的,所以只能使用${}。
/**
* 动态设置表名,查询所有的用户信息
* @param tableName
* @return
*/
List<User> getAllUser(@Param("tableName") String tableName);
<!--List<User> getAllUser(@Param("tableName") String tableName);-->
<select id="getAllUser" resultType="User">
select * from ${tableName}
</select>
只能使用${},因为表名不可以加单引号,因为#{}会自动加上单引号。
场景举例:假如我们要操作的两张表是1对多的关系的,比如学生表和班级表。我们新添加一个班级,同时想为这个班级添加一些学生信息。而添加学生信息的学生表中就有一个字段是班级表的主键id,所以需要获取这个班级id。
/**
* 添加用户信息
* @param user
* @return
* useGeneratedKeys:设置使用自增的主键
* keyProperty:因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参数user对象的某个属性中
*/
int insertUser(User user);
<!--int insertUser(User user);-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(null,#{username},#{password},#{age},#{sex})
</insert>
useGeneratedKeys设置为true。keyProperty属性的值就可以将自增主键的值设置到传入参数对象中的属性中。在实际使用MyBaits操作数据时会遇到一个问题:数据表的字段名和实体类的属性名不一致。
如果不解决这个问题,查询出来的字段值就是null。
例如:
员工信息表t_emp,表示员工名字的字段是emp_name,而实体类的属性是empName。
解决方案一:
在编写SQL语句的时候为不同的字段起别名:
select eid, emp_name `empName`, age, sex, email, did from t_emp;
解决方案二:
通过配置MyBatis配置文件开启自动将下划线命名的规则转换为驼峰命名规则。
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
更多可以参考官方文档:https://mybatis.org/mybatis-3/zh/configuration.html#settings
解决方案三:
使用resultMapresultMap处理字段和属性的映射关系。
<!--
resultMap:设置自定义映射
属性:
id:表示自定义映射的唯一标识
type:查询的数据要映射的实体类的类型
子标签:
id:设置主键的映射关系
result:设置普通字段的映射关系
association:设置多对一的映射关系
collection:设置一对多的映射关系
属性:
property:设置映射关系中实体类中的属性名
column:设置映射关系中表中的字段名
-->
<resultMap id="empResultMap" type="com.jc.mybatis.pojo.Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="email" column="email"></result>
<result property="sex" column="sex"></result>
<result property="age" column="age"></result>
</resultMap>
<select id="getAllEmp" resultMap="empResultMap">
select * from t_emp
</select>
一个部门中有多个员工,所以员工与部门的关系就是多对一。
想要查询员工信息时,想要把相对应的部门信息也查出来就需要在实体类中添加一个部门类类型的属性。
例如:
public class Emp {
private int eid;
private String empName;
private int age;
private String sex;
private String email;
// 用于存储部门信息
private Dept dept;
// set and get ...
}
<resultMap id="empAndDeptResultMapOne" type="com.jc.mybatis.pojo.Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<result property="age" column="age"></result>
<!--级联赋值-->
<result property="dept.did" column="did"></result>
<result property="dept.deptName" column="dept_name"></result>
</resultMap>
<select id="getEmpAndDeptById" resultMap="empAndDeptResultMapOne">
select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid}
</select>
<resultMap id="empAndDeptResultMapTwo" type="com.jc.mybatis.pojo.Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<result property="age" column="age"></result>
<!--
association:
property: 属性名
javaType: 属性对应的类型
-->
<association property="dept" javaType="com.jc.mybatis.pojo.Dept">
<result property="did" column="did"></result>
<result property="deptName" column="dept_name"></result>
</association>
</resultMap>
<select id="getEmpAndDeptById" resultMap="empAndDeptResultMapTwo">
select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid}
</select>
原理是MyBatis使用用association标签中的select属性设置另一个查询用的方法组装的方式分步查询。先查出员工信息,在通过查出员工表中的部门id查询对应的部门信息。
分布查询有一个好处,就是延迟加载。
EmpMapper.xml:
<resultMap id="getEmpAndDeptByStepOneResultMap" type="com.jc.mybatis.pojo.Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<result property="age" column="age"></result>
<association property="dept" select="com.jc.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo" column="did">
</association>
</resultMap>
<select id="getEmpAndDeptByStepOne" resultMap="getEmpAndDeptByStepOneResultMap">
select * from t_emp where eid = #{eid}
</select>
DeptMapper.xml:
<select id="getEmpAndDeptByStepTwo" resultType="com.jc.mybatis.pojo.Dept">
select * from t_dept where did = #{did}
</select>
EmpMapper.java中的方法:
Emp getEmpAndDeptByStepOne(@Param("eid") Integer id);
DeptMapper.java中的方法:
Dept getEmpAndDeptByStepTwo(@Param("did") Integer did);
查询结果:
2022-06-23 14:46:01 DEBUG getEmpAndDeptByStepOne:137 - ==> Preparing: select * from t_emp where eid = ?
2022-06-23 14:46:01 DEBUG getEmpAndDeptByStepOne:137 - ==> Parameters: 2(Integer)
2022-06-23 14:46:01 DEBUG getEmpAndDeptByStepTwo:137 - ====> Preparing: select * from t_dept where did = ?
2022-06-23 14:46:01 DEBUG getEmpAndDeptByStepTwo:137 - ====> Parameters: 1(Integer)
2022-06-23 14:46:01 DEBUG getEmpAndDeptByStepTwo:137 - <==== Total: 1
2022-06-23 14:46:01 DEBUG getEmpAndDeptByStepOne:137 - <== Total: 1
Emp{eid=2, empName='李四', age=22, sex='女', email='123@qq.com', dept=Dept{did=1, deptName='A'}}
延迟加载功能
要实现延迟加载需要在配置文件红设置全局配置信息:
lazyLoadingEnabled: 延迟加载的全局开关。当开启时,所有关联的对象都会延迟加载。
aggressiveLazyLoading: 当开启时,任何方法的调用都会加载该对象的所有属性。否则每个属性都会按需加载。此时就可以实现按需加载,获取的数据时什么,就只会执行响应的sql。
但是可以通过association或collection标签中的
fetchType属性设置当前的分步查询是否使用延迟加载,fetchType=“lazy(延迟加载)| eager(立即加载)”
MyBatis配置文件:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
EmpMapper.xml中的resultMap:
<resultMap id="getEmpAndDeptByStepOneResultMap" type="com.jc.mybatis.pojo.Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<result property="age" column="age"></result>
<association fetchType="lazy" property="dept" select="com.jc.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo" column="did">
</association>
</resultMap>
输出:
可以发现只有一条查询记录,如果将
fetchType的值设置为eager,就会关闭懒加载,也就是一次性把关联的属性都查询出来。
2022-06-23 15:11:07 DEBUG getEmpAndDeptByStepOne:137 - ==> Preparing: select * from t_emp where eid = ?
2022-06-23 15:11:07 DEBUG getEmpAndDeptByStepOne:137 - ==> Parameters: 2(Integer)
2022-06-23 15:11:07 DEBUG getEmpAndDeptByStepOne:137 - <== Total: 1
李四
一个部门对应了多个员工,在数据库中是在员工表中添加关于部门id的冗余字段来实现依赖关系。而在Java的实体类中使用集合的方式进行存储。
例如:
public class Dept {
private int did;
private String deptName;
private List<Emp> emps;
// set and get ...
}
DeptMapper.xml:
使用collection标签来设置集合,
ofType属性来指定集合中元素的类型。
<resultMap id="deptAndEmpResultMap" type="com.jc.mybatis.pojo.Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<collection property="emps" ofType="com.jc.mybatis.pojo.Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="sex" column="sex"></result>
<result property="age" column="age"></result>
<result property="email" column="email"></result>
</collection>
</resultMap>
<select id="getDeptAndEmpById" resultMap="deptAndEmpResultMap">
select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did}
</select>
DeptMapper.xml:
<resultMap id="dptAndEmpByStepResultMap" type="com.jc.mybatis.pojo.Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<collection
select="com.jc.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo"
property="emps"
column="did">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="sex" column="sex"></result>
<result property="age" column="age"></result>
<result property="email" column="email"></result>
</collection>
</resultMap>
<select id="getDeptAndEmpByStepOne" resultMap="dptAndEmpByStepResultMap">
select * from t_dept where did = #{did}
</select>
EmpMapper.xml:
<select id="getDeptAndEmpByStepTwo" resultType="com.jc.mybatis.pojo.Emp">
select * from t_emp where did = #{did}
</select>
DeptMapper.java:
Dept getDeptAndEmpByStepOne(@Param("did") Integer id);
EmpMapper.java:
Emp getDeptAndEmpByStepTwo(@Param("did") Integer did);
Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。
if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中的内容不会执行。
但是需要注意,如果没有一个条件成立,sql语句可能会拼接上一个and,导致报错。
所以可以拼接一个 1=1。
<select id="getEmpListByMoreTJ" resultType="Emp">
select * from t_emp where 1=1
<if test="ename != '' and ename != null">
and emp_name = #{empName}
</if>
<if test="age != '' and age != null">
and age = #{age}
</if>
<if test="sex != '' and sex != null">
and sex = #{sex}
</if>
</select>
where和if一般结合使用:
a> 若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字
b> 若where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的and去掉
注意:where标签不能去掉条件最后多余的and
<select id="getEmpListByMoreTJ2" resultType="Emp">
select * from t_emp
<where>
<if test="ename != '' and ename != null">
emp_name = #{empName}
</if>
<if test="age != '' and age != null">
and age = #{age}
</if>
<if test="sex != '' and sex != null">
and sex = #{sex}
</if>
</where>
</select>
trim用于去掉或添加标签中的内容。
常用属性:
prefix:在trim标签中的内容的前面添加某些内容。prefixOverrides:在trim标签中的内容的前面去掉某些内容。suffix:在trim标签中的内容的后面添加某些内容。suffixOverrides:在trim标签中的内容的后面去掉某些内容 。
<select id="getEmpListByMoreTJ" resultType="Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and">
<if test="ename != '' and ename != null">
emp_name = #{empName} and
</if>
<if test="age != '' and age != null">
age = #{age} and
</if>
<if test="sex != '' and sex != null">
sex = #{sex}
</if>
</trim>
</select>
choose、when、otherwise是一套标签,需要配合使用。
注意:使用时至少需要一个when,最多可以有一个otherwise。
<select id="getEmpListByChoose" resultType="Emp">
select <include refid="empColumns"></include> from t_emp
<where>
<choose>
<when test="ename != '' and ename != null">
emp_name = #{empName}
</when>
<when test="age != '' and age != null">
age = #{age}
</when>
<when test="sex != '' and sex != null">
sex = #{sex}
</when>
<when test="email != '' and email != null">
email = #{email}
</when>
</choose>
</where>
</select>
适用于批量操作,如批量添加或者批量删除。
属性:
collection:设置要循环的数组或集合。
item:表示集合或数组中的每一个数据。
separator:设置循环体之间的分隔符。
open:设置foreach标签中的内容的开始符。
close:设置foreach标签中的内容的结束符。
使用举例:
批量删除:
EmpMapper.java中的方法:
int deleteEmpBatch(@Param("eids") Integer[] eids);
- 1
<delete id="deleteEmpBatch">
delete from t_emp where eid in
<foreach collection="eids" item="eid" separator="," open="(" close=")">
#{eid}
</foreach>
</delete>
批量添加:
EmpMapper.java中的方法:
int insertEmpBatch(@Param("emps") List<Emp> emps);
- 1
<insert id="insertEmpBatch">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null, #{emp.empName}, #{emp.age}, #{emp.sex}, #{emp.email}, null)
</foreach>
</insert>
用于编写一个sql片段,可以记录一段公共sql片段,在使用的地方通过include标签进行引入。
使用举例:
<sql id="empColumns">
eid,emp_name,age,sex,email,did
</sql>
<select id="getAllEmp" resultMap="empResultMap">
select
<include refid="empColumns"></include>
from t_emp
</select>
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存 ,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问。
在MyBatis操作数据库时会创建一个sqlSession,在这个sqlSession中有一个map集合,用于存放缓存数据。
使一级缓存失效的4种情况:
二级缓存是SqlSessionFactory级别 ,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。
开启二级缓存的条件:
<settings>
<!-- 开启二级缓存(默认就是开启状态,但是方便维护,显式写出来 -->
<setting name="cacheEnabled" value="true" />
</settings>
<cache/>标签<!-- 开启本mapper所在namespace的二级缓存 -->
<cache />
Serializable接口。为了取出实现反序列化操作,因为二级缓存的方式可能有多种,存放到内存中或是写入到磁盘上。使二级缓存失效的情况:
两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效。
在mapper配置文件中添加的cache标签可以设置一些属性:
eviction属性:缓存回收策略,默认的是 LRU。
LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。flushInterval属性:刷新间隔,单位毫秒
size属性:引用数目,正整数
readOnly属性:只读,true/false
true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。注意: 整合的第三方缓存只能用于替换二级缓存。
1、导入相关依赖
<!-- Mybatis EHCache整合包 -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
<!-- slf4j日志门面的一个具体实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
2、创建EHCache的配置文件ehcache.xml
<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="E:\MyBatis\ehcache"/>
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
3、设置二级缓存的类型
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
4、加入加入logback日志
存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志。创建logback的配置文件logback.xml。
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger]
[%msg]%n
</pattern>
</encoder>
</appender>
<!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
<!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
<root level="DEBUG">
<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
<appender-ref ref="STDOUT"/>
</root>
<!-- 根据特殊需求指定局部日志级别 -->
<logger name="com.atguigu.crowd.mapper" level="DEBUG"/>
</configuration>
5、EHCache配置文件说明
| 属性名 | 是 否 必 须 | 作用 |
|---|---|---|
| maxElementsInMemory | 是 | 在内存中缓存的element的最大数目 |
| maxElementsOnDisk | 是 | 在磁盘上缓存的element的最大数目,若是0表示无 穷大 |
| eternal | 是 | 设定缓存的elements是否永远不过期。 如果为 true,则缓存的数据始终有效, 如果为false那么还 要根据timeToIdleSeconds、timeToLiveSeconds 判断 |
| overflowToDisk | 是 | 设定当内存缓存溢出的时候是否将过期的element 缓存到磁盘上 |
| timeToIdleSeconds | 否 | 当缓存在EhCache中的数据前后两次访问的时间超 过timeToIdleSeconds的属性取值时, 这些数据便 会删除,默认值是0,也就是可闲置时间无穷大 |
| timeToLiveSeconds | 否 | 缓存element的有效生命期,默认是0.,也就是 element存活时间无穷大 |
| diskSpoolBufferSizeMB | 否 | DiskStore(磁盘缓存)的缓存区大小。默认是 30MB。每个Cache都应该有自己的一个缓冲区 |
| diskPersistent | 否 | 在VM重启的时候是否启用磁盘保存EhCache中的数 据,默认是false。 |
| diskExpiryThreadIntervalSeconds | 否 | 磁盘缓存的清理线程运行间隔,默认是120秒。每 个120s, 相应的线程会进行一次EhCache中数据的 清理工作 |
| memoryStoreEvictionPolicy | 否 | 当内存缓存达到最大,有新的element加入的时 候, 移除缓存中element的策略。 默认是LRU(最 近最少使用),可选的有LFU(最不常使用)和 FIFO(先进先出) |
创建逆向工程的步骤:
<!-- 依赖MyBatis核心包 -->
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
</dependencies>
<!-- 控制Maven在构建过程中相关配置 -->
<build>
<plugins>
<!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<!-- 插件的依赖 -->
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.8</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--自定义properties配置文件的位置-->
<properties resource=""/>
<settings>
<!--弃用驼峰命名转换-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases></typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name=""/>
</mappers>
</configuration>
注意:文件名必须是:generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD(简洁版)
MyBatis3: 生成带条件的CRUD(高级版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3Simple">
<!-- 数据库的连接信息 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis"
userId="root"
password="123456">
</jdbcConnection>
<!-- javaBean的生成策略-->
<javaModelGenerator targetPackage="com.jc.mybatis.pojo"
targetProject=".\src\main\java">
<!--将enableSubPackages设置为true就是使用“ . ” 分割的是文件夹,而不是文件名-->
<property name="enableSubPackages" value="true"/>
<!--去掉首尾字符串-->
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="com.jc.mybatis.mapper"
targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.jc.mybatis.mapper" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
<!-- domainObjectName属性指定生成出来的实体类的类名 -->
<table tableName="t_emp" domainObjectName="Emp"/>
<table tableName="t_dept" domainObjectName="Dept"/>
</context>
</generatorConfiguration>
如果依赖导入成功,在mavne栏会有这个个插件,双击运行即可生成所配置的实体类,Mapper接口和Mapper文件。
注意:根据配置的不同,可以生成MyBatis3Simple版和MyBatis3版(可以使用QBC查询)。

执行逆向工程后生成的结果如下:
MyBatis3Simple 简洁版:

MyBatis3(高级版):
相较与simple版的多了XxxExample类,通过Example可以帮助我们实现QBC查询。

QBC即Quary By Criteria,Criteria是Criterion的复数,译为规则,准则,在sql语句中相当于查询条件。QBC查询是将查询条件通过Java对象进行模块化封装。
需要使用逆向工程生成的Example对象来实现。
使用举例:
@Test
public void testQBC() throws Exception {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// 创建对应的Example对象
EmpExample example = new EmpExample();
// 相当于拼接sql语句的条件
example.createCriteria().andAgeBetween(20, 30).andDidIsNotNull();
// 相当于sql中的or关键字拼接
example.or().andEmpNameEqualTo("a1");
List<Emp> emps = mapper.selectByExample(example);
emps.forEach(System.out::println);
}
打印结果:
2022-06-24 13:04:07 DEBUG selectByExample:137 - ==> Preparing: select eid, emp_name, age, sex, email, did from t_emp WHERE ( age between ? and ? and did is not null ) or( emp_name = ? )
2022-06-24 13:04:07 DEBUG selectByExample:137 - ==> Parameters: 20(Integer), 30(Integer), a1(String)
2022-06-24 13:04:07 DEBUG selectByExample:137 - <== Total: 4
Emp{eid=1, empName='张三', age=23, sex='男', email='123@qq.com', did=3}
Emp{eid=2, empName='李四', age=22, sex='女', email='123@qq.com', did=1}
Emp{eid=4, empName='赵六', age=25, sex='男', email='123@qq.com', did=2}
Emp{eid=9, empName='a1', age=20, sex='男', email='123456789@qq.com', did=null}
可以执行的sql语句是:
select eid, emp_name, age, sex, email, did from t_emp WHERE ( age between ? and ? and did is not null ) or( emp_name = ? )
添加依赖:
从依赖就可以看出,分页插件是一个拦截器
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
在主配置文件中开启插件:
<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
@Test
public void testPagehelper1() throws Exception {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// 返回的对象是查询后的分页对象
// 必须执行在查询前,并且一定要执行查询功能。
Page<Emp> page = PageHelper.startPage(1, 5);
List<Emp> emps = mapper.selectByExample(null);
System.out.println("当前页码: " + page.getPageNum());
System.out.println("总页数: " + page.getPages());
System.out.println("每页数量: " + page.getPageSize());
System.out.println("总记录数: " + page.getTotal());
System.out.println("当前页的第一条数据的索引: " + page.getStartRow());
}
@Test
public void testPagehelper2() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// 返回的对象是查询后的分页对象
// 必须执行在查询前,并且一定要执行查询功能。
Page<Emp> page = PageHelper.startPage(1, 5);
mapper.selectByExample(null);
PageInfo<Emp> pageInfo = new PageInfo<>(page);
System.out.println(pageInfo.isIsFirstPage()); //是否是第一页
System.out.println(pageInfo.isHasNextPage()); //是否有下一页
System.out.println(pageInfo.getNextPage()); //获取下一页的页码
System.out.println(pageInfo.isHasPreviousPage()); //是否有上一页
System.out.println(pageInfo.getPageNum()); //获取当前页码数
System.out.println(pageInfo.getTotal()); //获取总记录数
System.out.println(pageInfo.getPages()); //获取总页数
System.out.println(pageInfo.getPageSize()); //获取一页的记录
System.out.println(pageInfo);
}