Mybatis
Mybatis简介
1、Mybatis历史
Mybatis最初是Apache的一个开源项目iBatis,2010年6月这个项目由 Apache Software Foundation迁移到了 Google Code。随着开发团队转投Google Code 旗下,iBatis3.x正式更名为Mybatis。代码于2013年11月迁移到GitHub。
iBatis一词来源于“Internet”和“abatis”的组合,是一个基于Java的持久层框架。iBatis提供的持久层框架包括SQLMaps和Data Access Objects(DAO)。
2、Mybatis特性
(1)Mybatis是支持定制化sql、存储过程以及高级映射的优秀的持久层框架。
(2)Mybatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。
(3)Mybatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO映射成数据库中的记录。
(4)Mybatis是一个 半自动的ORM(Object Relation Mapping)框架。
3、mybatis下载
mybatis下载地址
4、Mybatis和其他持久层技术的对比
- JDBC
- SQL夹杂在java代码中耦合度高,导致硬编码内伤。
- 维护不易且实际开发需求中sql有变化,频繁修改的情况比较多。
- 代码冗长,开发效率低
- Hibernate和JPA
- 操作简单,开发效率高
- 程序中的长难复杂sql需要绕过框架
- 内部自动生产的sql,不容易做特殊优化
- 基于全映射的全自动框架,大量字段的pojo进行部分映射时比较困难
- 反射操作太多,导致数据库性能下降
- Mybatis
- 轻量级,性能出色
- SQL和Java编码分开,功能边界清晰。Java代码专注业务,sql语句专注数据
- 开发效率稍逊于Hibernate,但是完全能够接受
Mybatis框架搭建
1、创建maven工程,引入依赖
<!--mybatis核心-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</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.6</version>
</dependency>
2、配置mybatis的核心配置文件
习惯上命名为mybatis-config.xml,这个文件名仅仅只是建议,并非强制要求。将来整合Spring之后,这个配置文件可以省略,所以操作时可以直接复制粘贴。
核心配置文件主要用于配置连接数据库的环境以及mybatis的全局配置信息。
核心配置文件存放的位置是 src/main/resourses目录下。
<?xml version="1.0" encoding="UTF8" ?>
<!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.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="qwer`123"/>
</dataSource>
</environment>
</environments>
<!-- 引入映射文件 -->
<mappers>
<mapper resource=""/>
</mappers>
</configuration>
3、创建mapper接口
Mybatis中的mapper接口相当于以前的dao。但是区别在于,mapper仅仅是接口,我们不需要提供实现类。
(1)创建数据库表:t_user,设置主键自增
(2)创建实体类
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
private String sex;
private String email;
...
(3)创建mapper接口
4、创建Mybatis的映射文件
相关概念:ORM(Object Relationship Mapping)对象关系映射。
- 对象:Java的实体类对象
- 关系:关系型数据库
- 映射:二者之间的对应关系
Java概念 | 数据库概念 |
---|---|
类 | 表 |
属性 | 字段/列 |
对象 | 记录/行 |
(1)映射文件的命名规则:表所对应的实体类的类名+Mapper.xml。因此,一个映射文件对应一个实体类,对应一张表的操作。映射文件用于编写sql,访问以及操作表中的数据。
(2)mybatis中可以面向接口操作数据,要保证两个一致:
mapper接口的全类名和映射文件的命名空间(namespace)保持一致;
mapper接口中方法的方法名和映射文件中编写sql的标签的id属性保持一致。
映射文件存放的位置是src/main/resource/mappers目录下
<?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.hxp.mybatis.mapper.UserMapper">
<insert id="insertUser">
insert into t_user values(null,'admin','123456',23,'男','[email protected]')
</insert>
</mapper>
想清楚mybatis中的对应关系:表 – 实体类 – mapper接口 – 映射文件
Mybatis基础功能
增删改查
添加功能
public interface UserMapper {
/**
* 添加用户信息
* @return
*/
int insertUser();
}
在mybatis核心配置文件,映入映射文件。注意,这里是用 resource 进行引入的,所以要写UserMapper.xml的路径,UserMapper.xml放在resources的mappers文件下。
<!-- 引入映射文件 -->
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
测试使用
- sqlSession:代表Java程序和数据库之间的会话。(HttpSession是java程序和浏览器之间的会话)。
- SqlSessionFactory:是“生产”SqlSession的“工厂”。
- 工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象。
@Test
public void test1() throws IOException {
//加载核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取mapper接口对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//测试功能
int result = userMapper.insertUser();
//提交事务
sqlSession.commit();
System.out.println("result:" + result);
}
优化1:设置自动提交,给openSession()的参数设置为true,默认为false不自动提交。
优化2:加入log4j日志功能。引入依赖,加入log4j的配置文件。
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
log4j的配置文件名为log4j.xml,存放的位置是src/main/resources目录下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n"/>
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT"/>
</root>
</log4j:configuration>
日志级别
FATAL(致命)> ERROR(错误)> WARN(警告)> INFO(信息)> DEBUG(调试)
从左到右打印的内容越来越详细
修改和删除功能
/**
* 修改用户信息
*/
void updateUser();
/**
* 删除用户信息
*/
void deleteUser();
<!-- void updateUser(); -->
<update id="updateUser">
update t_user set username = '张三' where id = 2;
</update>
<!-- void deleteUser(); -->
<delete id="deleteUser">
delete from t_user where id = 5;
</delete>
查询功能
/**
* 查询用户信息
*/
User selectUserById();
/**
* 查询所有用户信息
*/
List<User> selectAllUser();
<!-- User selectUserById(); -->
<select id="selectUser" resultType="com.hxp.mybatis.pojo.User">
select * from t_user where id = 1;
</select>
<!-- List<User> selectAllUser(); -->
<select id="selectAllUser" resultType="com.hxp.mybatis.pojo.User">
select * from t_user;
</select>
查询功能的标签必须设置 resultType 或 resultMap
- resultType:设置默认的映射关系
- resultMap:设置自定义的映射关系
核心配置文件
- environments:配置多个连接数据库的环境
- 属性:default,设置默认使用的环境id
- environment:配置某个具体的环境
- 属性:id,表示连接数据库的环境的唯一标识,不能重复。
- transactionManager:设置事务管理方式
- 属性:type=“JDBC|MANAGER”,JDBC:表示当前环境中,执行sql时,使用的是JDBC中原生的事务管理方式,事务的提交或回滚需要手动处理;MANAGER:被管理,例如Spring的声明式事务。
- dataSource:配置数据源
- 属性:type:设置数据源的类型,type=“POOLED|UNPOOLED|JNDI”。POOLED:表示使用数据库连接池缓存数据库连接;UNPOOLED:表示不使用数据库连接池;JNDI:表示使用上下文中的数据源。
<!--配置连接数据库的环境-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--设置连接数据库的驱动-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<!--设置连接数据库的连接地址-->
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<!--设置连接数据库的用户名-->
<property name="username" value="root"/>
<!--设置连接数据库的密码-->
<property name="password" value="qwer`123"/>
</dataSource>
</environment>
</environments>
- properties标签
jdbc.properties:<!--引入properties文件--> <properties resource="jdbc.properties" />
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8 jdbc.username=root jdbc.password=qwer`123
<property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/>
注意:配置文件中的标签有顺序,不按照顺序编写会报错
-
typeAliases
<typeAliases> <typeAlias type="com.hxp.mybatis.pojo.User" alias="User"></typeAlias> </typeAliases>
类型别名不区分大小写。不加alias属性,默认别名为类名且不区分大小写。
以包为单位,将包下所有的类型设置默认的类型别名,且不区分大小写<typeAliases> <package name="com.hxp.mybatis.pojo"/> </typeAliases>
java中各个类型对应的别名:
-
mappers
<!-- 引入映射文件 --> <mappers> <mapper resource="mappers/UserMapper.xml"/> </mappers>
如果接口和xml映射文件多起来的话,用这种方式引入要写很多重复的代码,建议以包为单位引入映射文件:
<mappers> <package name="com.hxp.mybatis.mapper"/> </mappers>
注意,在resources下面创建包,只能创建Directory,中间要用 / 隔开,中间用点隔开 创建的只是一个文件。
注意,这样引入映射文件,映射文件的包要和接口的包一致,mapper接口的名字要和映射文件的名字一致
在idea中设置核心配置文件的模板
Mybatis获取参数值的方式
idea设置映射文件的模板
SqlSession工具类
public class SqlSessionUtils {
public static SqlSession getSqlSession () {
SqlSession sqlSession = null;
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
sqlSession = sqlSessionFactory.openSession(true);
} catch (IOException e) {
e.printStackTrace();
}
return sqlSession;
}
}
1、mybatis获取参数的两种方式:${}
和 #{}
${}
:本质是字符串拼接#{}
:本质是占位符赋值
2、mapper接口方法的参数为单个的字面量类型
可以通过${}或#{},以任意的名称获取参数值。
/**
* 根据username获取对象
*/
User getUserByUsername(String username);
使用#{}获取参数:
<!-- User getUserByUsername(String username); -->
<select id="getUserByUsername" resultType="User">
select * from t_user where username=#{username}
</select>
使用${}获取参数。因为本质是字符串拼接,所以需要加上单引号,不然报错
<!-- User getUserByUsername(String username); -->
<select id="getUserByUsername" resultType="User">
select * from t_user where username='${username}'
</select>
3、mapper接口方法的参数有多个时
此时,mybatis会将这些参数放在一个map集合中,以两种方式进行存储:
- 以arg0, arg1…为键,以参数为值,通过键获取值
- 以param1, param2…为键,以参数为值,通过键获取值
因此,需要通过${}或#{},中间传入键的方式进行访问。
/**
* 验证登录
*/
User checkLogin(String username, String password);
<!-- User checkLogin(String username, String password);-->
<select id="checkLogin" resultType="User">
select * from t_user where username=#{username} and password=#{password}
</select>
正确写法:
<!-- User checkLogin(String username, String password);-->
<select id="checkLogin" resultType="User">
select * from t_user where username=#{param1} and password=#{param2}
</select>
4、若mapper接口方法的参数有多个,可以手动将这些参数放在一个map中存储。
通过${}或#{},中间传入键的方式进行访问,键是我们自己设置的。
/**
* 验证登录,通过map集合
*/
User checkLoginByMap(Map<String, Object> map);
<!-- User checkLoginByMap(Map<String, Object> map);-->
<select id="checkLoginByMap" resultType="User">
select * from t_user where username=#{username} and password=#{password}
</select>
@Test
public void testCheckLoginByMap() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("username", "admin1");
map.put("password", "123456");
User user = mapper.checkLoginByMap(map);
System.out.println(user);
}
5、mapper接口方法的参数是实体类类型
通过${}或#{},以属性的方式访问属性值。
/**
* 添加一条数据
*/
int insertUser(User user);
<!-- int insertUser(User user);-->
<insert id="insertUser">
insert into t_user values (null, #{username}, #{password}, #{age}, #{sex},#{email})
</insert>
6、使用@Param注解命名参数
此时mybatis会将这些参数放在一个map集合中,以两种方式进行存储:
- 以@Param注解的值为键,以参数为值
- 以param1, param2…为键,以参数为值
/**
* 验证登录,通过@Param注解
*/
User checkLoginByParam(@Param("username") String username, @Param("password") String password);
<!-- User checkLoginByParam(@Param("username") String username, @Param("password") String password);-->
<select id="checkLoginByParam" resultType="User">
select * from t_user where username=#{username} and password=#{password}
</select>
mybatis的各种查询功能
1、查询数据,用list集合接收
若查询出的数据只有一条,可以通过实体类对象或者集合接收;若查询出的数据有多条,可以通过list集合接收。
/**
* 根据id查询用户
*/
List<User> getUserById(@Param("id") Integer id);
/**
* 查询所有用户
*/
List<User> getAllUser();
<!-- List<User> getUserById(@Param("id") Integer id);-->
<select id="getUserById" resultType="User">
select * from t_user where id = #{id}
</select>
<!-- List<User> getAllUser();-->
<select id="getAllUser" resultType="User">
select * from t_user;
</select>
2、查询表的记录数
/**
* 查询用户总记录数
*/
Integer getCount();
别名:java.lang.Integer --> int, integer;int --> _int, _integer
<!-- Integer getCount();-->
<select id="getCount" resultType="Integer">
select count(*) from t_user;
</select>
3、查询数据,用map集合接收
/**
* 根据id查询数据,map接收
*/
Map<String,Object> getUserByIdToMap(@Param("id") Integer id);
<!-- Map<String,Object> getUserByIdToMap(@Param("id") Integer id);-->
<select id="getUserByIdToMap" resultType="map">
select * from t_user where id = #{id}
</select>
4、查询所有数据到map集合,用map类型的list集合接收
/**
* 查询所有用户信息为map集合
*/
List<Map<String, Object>> getAllUserToMap();
<!-- List<Map<String, Object>> getAllUserToMap();-->
<select id="getAllUserToMap" resultType="map">
select * from t_user;
</select>
方式二:在mapper接口的方法上添加 @MapKey注解,此时就可以将每条数据转换的map集合作为值,以某个字段的值作为键,放在同一个map集合。
@MapKey("id")
Map<String, Object> getAllUserToMap();
<select id="getAllUserToMap" resultType="map">
select * from t_user;
</select>
特殊sql的执行
1、模糊查询
/**
* 根据用户名模糊查询用户信息
*/
List<User> getUserByLike(@Param("username") String username);
模糊查询方式一:${}拼接字符串
<!-- List<User> getUserByLike(@Param("username") String username);-->
<select id="getUserByLike" resultType="user">
select * from t_user where username like '%${username}%'
</select>
模糊查询方式二:利用concat()方法
<select id="getUserByLike" resultType="user">
select * from t_user where username like concat('%',#{username},'%')
</select>
模糊查询方式三:
<select id="getUserByLike" resultType="user">
select * from t_user where username like "%"#{username}"%"
</select>
2、批量删除
注意:使用这种方式批量删除,不能用#{},因为#{}会拼接上引号,类似下面这条sql,这样不对。
delete from t_user where id in ('1,2,3')
所以,在使用 in 关键字做批量删除时,只能用${}
/**
* 批量删除
*/
int deleteMore(@Param("ids") String ids);
<!-- int deleteMore(@Param("ids") String ids);-->
<delete id="deleteMore">
delete from t_user where id in (${ids})
</delete>
3、动态设置表名
设置表名也只能用${},因为sql中的表名是不能加引号的,用#{}会加上引号。
/**
* 动态设置表名
*/
List<User> getUserByTableName(@Param("tableName") String tableName);
<!-- List<User> getUserByTableName();-->
<select id="getUserByTableName" resultType="User">
select * from ${tableName}
</select>
4、获取添加功能自增的主键
原来添加了一条数据,对象中id是没有值的,因为设置了主键自增,没有给id传值。
/**
*添加数据,获取自增的主键
*/
void insertUser(User user);
<!-- void insertUser(User user);-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user values (null, #{username}, #{password}, #{age}, #{sex},#{email})
</insert>
- useGeneratedKeys:设置当前标签中的sql使用了自增的主键。
- keyProperty:将自增的主键的值赋值给传输到映射文件中参数的某个属性上,这里传输到id属性上。
自定义映射resultMap
创建两张表:t_emp、t_dept
创建表对应的实体类,mapper接口和xml文件。
public class Emp {
private Integer eid;
private String empName;
private Integer age;
private String sex;
private String email;
public class Dept {
private Integer did;
private String deptName;
1、解决字段名和属性名不一致的情况
- 为字段起别名,保持和属性名一致
/** * 查询所有员工 */ List<Emp> getAllEmp();
<select id="getAllEmp" resultType="Emp"> select * from t_emp </select>
此时查出来为empName为null,因为表中的字段名是emp_name。<select id="getAllEmp" resultType="Emp"> select eid, emp_name empName, age, sex, email from t_emp </select>
- 设置全局配置,将_自动映射为驼峰
<!--设置mybatis的全局配置--> <settings> <!-- 将_自动映射为驼峰,emp_name:empName --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
<select id="getAllEmp" resultType="Emp"> select * from t_emp; </select>
- 通过resultMap设置自定义的映射关系
<resultMap id="empResultMap" type="Emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> </resultMap> <select id="getAllEmp" resultMap="empResultMap"> select * from t_emp </select>
- resultMap:设置自定义映射关系
- id:唯一标识,不能重复
- type:设置映射关系中的实体类类型
- 子标签
- id:设置主键的映射关系
- result:设置普通字段的映射关系
- property:设置映射关系中的属性名,必须是type属性所设置的实体类类型中的属性名
- column:设置映射关系中的字段名,必须是sql语句查询出的字段名
2、多对一的映射关系
- 通过级联属性赋值解决多对一的映射关系
在Emp实体类,添加一个Dept属性,并生成get和set方法/** * 根据eid查询员工信息 */ Emp getEmpById(@Param("eid") Integer eid);
<resultMap id="empAndDeptResultMap" type="Emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> <result property="dept.did" column="did"></result> <result property="dept.deptName" column="dept_name"></result> </resultMap> <!-- Emp getEmpById(@Param("eid") Integer eid);--> <select id="getEmpById" resultMap="empAndDeptResultMap"> select * from t_emp e left join t_dept d on e.did = d.did where e.eid = #{eid} </select>
- 通过association解决多对一的映射关系
- association:处理多对一的映射关系
- property:需要处理多对一映射关系的属性名
- javaType:该属性的类型
<resultMap id="empAndDeptResultMap2" type="Emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> <association property="dept" javaType="Dept"> <id property="did" column="did"></id> <result property="deptName" column="dept_name"></result> </association> </resultMap>
- 通过分布查询解决多对一的映射关系
写两条sql,一条查询Emp员工,一条查询Dept部门
在EmpMapper接口中,编写查询员工的方法:/** * 通过分布查询员工以及员工所对应的部门信息 * 分布查询第一步:查询员工信息 */ Emp getEmpAndDeptByStep1(@Param("eid") Integer eid);
在DeptMapper接口中,编写查询部门的方法:<!-- Emp getEmpAndDeptByStep1(@Param("eid") Integer eid);--> <select id="getEmpAndDeptByStep1" resultMap="empAndDeptResultMapByStep"> select * from t_emp where eid = #{eid} </select>
/** * 通过分布查询员工以及员工所对应的部门信息 * 分布查询第二步:通过did查询员工所对应的部门 */ Dept getEmpAndDeptByStep2(@Param("did") Integer did);
最后,在EmpMapper.xml中,编写分布查询的映射关系<!-- Dept getEmpAndDeptByStep2(@Param("did") Integer did);--> <select id="getEmpAndDeptByStep2" resultMap="getDept"> select * from t_dept where did = #{did} </select>
- select:设置分布查询的sql的唯一标识(
namespace.sql的id
或 mapper接口的全类名.方法名
) - column:设置分布查询的条件
测试<resultMap id="empAndDeptResultMapByStep" type="Emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> <association property="dept" select="com.hxp.mybatis.mapper.DeptMapper.getEmpAndDeptByStep2" column="did"></association> </resultMap>
分布查询的优点:可以实现延迟加载,但是需要在核心配置文件中设置全局配置信息@Test public void test2() { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); EmpMapper mapper = sqlSession.getMapper(EmpMapper.class); Emp emp = mapper.getEmpAndDeptByStep1(1); System.out.println(emp); }
- lazyLoadingEnable:延迟加载的全局开关,开启后,所有关联对象都会延迟加载。默认为false。
- aggressiveLazyLoading:开启后,任何地方的调用都会加载该对象的所有属性。否则,每个对象都会按需加载。默认为false。
- select:设置分布查询的sql的唯一标识(
3、延迟加载
通俗来说,延迟加载就是查询什么信息,就只执行对应的sql。
<!--设置mybatis的全局配置-->
<settings>
<!--开启延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
比如,上面我们查询出了emp,对应执行的sql语句为:
如果我们只查询emp对象中的某个属性,则不会执行查询dept的sql
不开启延迟加载,就是把刚才编写的全局配置注释掉
所以,上面使用分布查询的好处就在这里,可以使用延迟加载,查询员工就执行员工的sql,查询部门就只执行部门的sql,想查询全部再一起执行。
有时候,我们开启了全局的延迟加载,但某些sql不想要延迟加载,就想立即加载。
我们可以用fetchType属性设置:
fetchType:当开启了全局的延迟加载之后,可通过此属性手动控制延迟加载的效果。(fetchType=“lazy|eager”,lazy表示延迟加载,eager表示立即加载)
4、一对多的映射关系
在Dept类中,加上Emp的集合属性,并编写get、set和toString方法。
-
通过collection解决一对多的映射关系
/** * 获取部门以及部门中所有的员工信息 */ Dept getDeptAndEmp(@Param("did") Integer did);
<resultMap id="deptAndEmpResultMap" type="Dept"> <id property="did" column="did"></id> <result property="deptName" column="dept_name"></result> <collection property="emps" ofType="Emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> </collection> </resultMap> <!-- Dept getDeptAndEmp(@Param("id") Integer id);--> <select id="getDeptAndEmp" resultMap="deptAndEmpResultMap"> select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did} </select>
- collection:处理一对多的映射关系
- ofType:表示该属性所对应的集合中存储数据的类型
-
通过分布查询解决一对多的映射关系
/** * 通过分布查询部门以及部门中所有的员工信息 * 分步查询第一步:查询部门信息 */ Dept getDeptAndEmpByStep1(@Param("did") Integer did);
<resultMap id="deptAndEmpResultMapByStep" type="Dept"> <id property="did" column="did"></id> <result property="deptName" column="dept_name"></result> <collection property="emps" select="com.hxp.mybatis.mapper.EmpMapper.getDeptAndEmpByStep2" column="did"> </collection> </resultMap> <!-- Dept getDeptAndEmpByStep1(@Param("did") Integer did);--> <select id="getDeptAndEmpByStep1" resultMap="deptAndEmpResultMapByStep"> select * from t_dept where did = #{did} </select>
/** * 通过分步查询部门以及部门中所有的员工信息 * 分步查询第二步:根据did查询员工信息 */ Emp getDeptAndEmpByStep2(@Param("eid") Integer eid);
<!-- Emp getDeptAndEmpByStep2(@Param("eid") Integer eid);--> <select id="getDeptAndEmpByStep2" resultType="Emp"> select * from t_emp where eid = #{eid} </select>
动态SQL
Mybatis框架的动态sql技术,是一种根据特定条件动态拼接sql语句的功能,它存在的意义是为了解决拼接sql语句字符串时的痛点问题。
if标签
根据标签中test属性所对应的表达式决定标签中的内容是否需要拼接到sql中。
/**
* 多条件查询
*/
List<Emp> getEmpByCondition(Emp emp);
<!-- List<Emp> getEmpByCondition(Emp emp);-->
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp where 1=1
<if test="empName != null and empName !=''">
and emp_name = #{empName}
</if>
<if test="age != null and age !=''">
and age = #{age}
</if>
<if test="sex != null and sex !=''">
and sex = #{sex}
</if>
<if test="email != null and email !=''">
and email = #{email}
</if>
</select>
测试:empName传入空值
@Test
public void test() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
List<Emp> emps = mapper.getEmpByCondition(new Emp(null, "", 23, "男", "[email protected]"));
System.out.println(emps);
}
where标签
当where标签中有内容时,会自动生成where关键字,并且将内容前多余的and或or去掉。
当where标签中没有内容时,此时where标签没有任何效果。
注意:where标签不能将其中内容后面多余的and或or去掉。
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<where>
<if test="empName != null and empName !=''">
and emp_name = #{empName}
</if>
<if test="age != null and age !=''">
and age = #{age}
</if>
<if test="sex != null and sex !=''">
and sex = #{sex}
</if>
<if test="email != null and email !=''">
and email = #{email}
</if>
</where>
</select>
测试:设置empName和age为空
@Test
public void test() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
List<Emp> emps = mapper.getEmpByCondition(new Emp(null, "", null, "男", "[email protected]"));
System.out.println(emps);
}
trim标签
若标签中有内容时:
prefix | suffix
:将trim标签中内容前面或后面添加指定内容suffixOverrides | prefixOverrides
:将trim标签中内容前面或后面去掉指定内容
若标签中没有内容时,trim标签也没有任何效果
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and|or">
<if test="empName != null and empName !=''">
emp_name = #{empName} and
</if>
<if test="age != null and age !=''">
age = #{age} and
</if>
<if test="sex != null and sex !=''">
sex = #{sex} or
</if>
<if test="email != null and email !=''">
email = #{email} and
</if>
</trim>
</select>
@Test
public void test() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
List<Emp> emps = mapper.getEmpByCondition(new Emp(null, "张三", null, "男", "[email protected]"));
System.out.println(emps);
}
choose、when、otherwise标签
相当于 if … else if … else,when至少有一个,otherwise最多一个。
/**
* 测试choose、when、otherwise
*/
List<Emp> getEmpByChoose(Emp emp);
<!-- List<Emp> getEmpByChoose(Emp emp);-->
<select id="getEmpByChoose" resultType="Emp">
select * from t_emp
<where>
<choose>
<when test="empName != null and empName !=''">
emp_name = #{empName}
</when>
<when test="age != null and age !=''">
age = #{age}
</when>
<when test="sex != null and sex !=''">
sex = #{sex}
</when>
<when test="email != null and email !=''">
email = #{email}
</when>
<otherwise>
eid = 1
</otherwise>
</choose>
</where>
</select>
测试
@Test
public void test2() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
List<Emp> emps = mapper.getEmpByChoose(new Emp(null, "张三", 18, "男", ""));
emps.forEach(emp -> System.out.println(emp));
}
@Test
public void test2() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
List<Emp> emps = mapper.getEmpByChoose(new Emp(null, "", null, "", ""));
emps.forEach(emp -> System.out.println(emp));
}
foreach标签
foreach标签中的属性:
- collection:设置需要循环的数组或集合
- item:表示数组或集合中的每一个数据
- separator:循环体之间的分隔符
- open:foreach标签所循环的所有内容的开始符
- close:foreach标签所循环的所有内容的结束符
1、批量删除
/**
* 通过数组实现批量删除
*/
int deleteMoreByArray(@Param("eids") Integer[] eids);
第一种方式:利用 in 关键字进行删除
<!-- int deleteMoreByArray(@Param("eids") Integer[] eids);-->
<delete id="deleteMoreByArray">
delete from t_emp where eid in
<foreach collection="eids" item="eid" separator="," open="(" close=")">
#{eid}
</foreach>
</delete>
第二种方式:利用 or 关键字进行删除
<delete id="deleteMoreByArray">
delete from t_emp where
<foreach collection="eids" item="eid" separator="or">
eid = #{eid}
</foreach>
</delete>
2、批量添加
/**
* 通过集合实现批量添加
*/
int insertMoreByList(@Param("emps") List<Emp> emps);
<!-- int insertMoreByList(@Param("eids") Integer[] eids);-->
<insert id="insertMoreByList">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null, #{emp.empName}, #{emp.age}, #{emp.sex}, #{emp.email}, null)
</foreach>
</insert>
sql标签
Mybatis缓存
mybatis一级缓存
一级缓存是sqlSession级别的,通过同一个sqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问。
/**
* 验证缓存
*/
Emp getEmpByEid(@Param("eid") Integer eid);
<select id="getEmpByEid" resultType="Emp">
select * from t_emp where eid = #{eid}
</select>
@Test
public void testCache() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
Emp emp1 = mapper.getEmpByEid(1);
System.out.println(emp1);
Emp emp2= mapper.getEmpByEid(1);
System.out.println(emp2);
}
结果:调用了两次查询方法,只执行了一次sql,说明第二次的数据是从缓存中获取的。
mybatis默认使用的一级缓存,作用范围是SqlSession级别。同一个sqlSession创建出来的mapper,会放入一个缓存,不同的sqlSession创建出来的mapper,放入不同的缓存。
一级缓存失效的四种情况:
- 不同的sqlSession对应不同的一级缓存
- 同一个sqlSession,但查询条件不同(缓存没有这个数据)
- 同一个sqlSession两次查询期间执行了任何一次增删改操作
- 同一个sqlSession两次查询期间手动清空了缓存(SqlSession的clearCache()方法)
Mybatis的二级缓存
二级缓存是SqlSessionFactory级别的,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。
二级缓存开启的条件:
-
在核心配置文件中,设置全局配置属性cacheEnabled=“true”,默认为true,不需要设置
-
在映射文件中设置标签
<chche />
-
二级缓存必须在SqlSession关闭或提交之后有效
-
查询的数据所转换的实体类类型必须实现序列化的接口
注意:不能用之前编写的SqlSessionUtils去创建sqlSession,因为这样使用了不同的SqlSessionFactory去创建SqlSession。
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
System.out.println(mapper1.getEmpByEid(1));
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
System.out.println(mapper2.getEmpByEid(1));
sqlSession2.close();
} catch (IOException e) {
e.printStackTrace();
}
二级缓存失效的情况:
两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效。
二级缓存的相关配置
在mapper配置文件中添加了cache标签可以设置一些属性:
- eviction:缓存回收策略
LRU – 最近最少使用,移除最上时间上不被使用的对象。
FIFO – 先进先出,按对象进入缓存的顺序来移除它们。
SOFT – 软引用,移除基于垃圾回收器状态和软引用规则的对象
WERK – 弱引用,更积极地移除基于垃圾回收器状态和弱引用规则的对象
默认是LRU。 - flushInterval:刷新间隔,单位毫秒。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
- size:引用数目,正整数。代表缓存最多可以存储多少个对象,太大容易导致内存溢出。
- readOnly:只读,true/false
true:只读缓存,会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改。这提供了很重要的性能优势。
false:读写缓存,会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
Mybatis缓存查询的顺序
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- sqlSession没有关闭之前,查询出来的数据保存在sqlSession的一级缓存中;sqlSession关闭之后,一级缓存中的数据会写入二级缓存。
整合第三方缓存EHCache
代替mybatis的二级缓存。
<!--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>
新建一个文件,名称叫"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="D:\upload\ehcache" />
<defaultCache
maxElementsInMemory="10000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
在mapper配置文件,设置二级缓存的类型
<cache type="org.mybatis.caches.ehcache.EhcacheCache" />
加入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-ref ref="STDOUT"/>
</root>
<!-- 根据特殊需求指定局部日志级别 -->
<logger name="com.hxp.mybatis.mapper" level="DEBUG" />
</configuration>
Mybatis的分页插件
mysql的分页:limit index,pageSize
- index:当前页的起始索引
- pageSize:每页显示的条数
- pageNum:当前页的页码
- index = (pageNum-1) * pageSize
Mybatis分页插件使用步骤:
1、添加依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
2、在核心配置文件中配置分页插件
<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
3、使用:在查询功能之前开启分页
@Test
public void test() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
PageHelperMapper mapper = sqlSession.getMapper(PageHelperMapper.class);
PageHelper.startPage(2,4);
List<Emp> empList = mapper.getAllEmpPage();
empList.forEach(emp -> System.out.println(emp));
}
4、查询功能之后获取分页相关信息
查看PageHelper.startPage(2,4)
方法得到的page对象
@Test
public void test() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
PageHelperMapper mapper = sqlSession.getMapper(PageHelperMapper.class);
Page<Object> page = PageHelper.startPage(2, 4);
List<Emp> empList = mapper.getAllEmpPage();
System.out.println(page);
}
输出内容:里面包含了分页的各种信息
Page{count=true, pageNum=2, pageSize=4, startRow=4, endRow=8, total=8, pages=2, reasonable=false, pageSizeZero=false}[Emp{eid=5, empName=‘田七’, age=32, sex=‘女’, email=‘[email protected]’, dept=null}, Emp{eid=10, empName=‘a’, age=18, sex=‘男’, email=‘[email protected]’, dept=null}, Emp{eid=11, empName=‘b’, age=19, sex=‘男’, email=‘[email protected]’, dept=null}, Emp{eid=12, empName=‘c’, age=20, sex=‘男’, email=‘[email protected]’, dept=null}]
通过PageInfo对象,可以获取到更多的信息
@Test
public void test() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
PageHelperMapper mapper = sqlSession.getMapper(PageHelperMapper.class);
Page<Object> page = PageHelper.startPage(2, 2);
List<Emp> empList = mapper.getAllEmpPage();
//empList表示分页数据,3表示当前导航分页的数量
PageInfo<Emp> pageInfo = new PageInfo<>(empList, 3);
System.out.println(pageInfo);
}
PageInfo{pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=8, pages=4, list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=8, pages=4, reasonable=false, pageSizeZero=false}
[Emp{eid=3, empName=‘王五’, age=19, sex=‘男’, email=‘[email protected]’, dept=null}, Emp{eid=4, empName=‘小刘’, age=20, sex=‘女’, email=‘[email protected]’, dept=null}],
prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=3, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]}
常用数据:
- pageNum:当前页的页码
- pageSize:每页显示的条数
- size:当前页显示的真实条数
- total:总记录数
- pages:总页数
- prePage:上一页的页码
- nextPage:下一页的页码
- isFirstPage/isLastPage:是否为第一页/最后一页
- hasPreviousPage/hasNextPage:是否存在上一页/下一页
- navigatePages:导航分页的页码数
- navigatepageNums:导航分页的页码 [1,2,3,4,5]