工具类
public class SqlSessionFactoryUtil {
private static InputStream is;
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//读取核心配置文件
is = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactoryBuilder对象 SqlSessionFactory对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//获取sqlSession对象/参数是否默认自动提交事务 默认不自动提交 设置为true
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
//关闭流
public static void close(){
try {
is.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
作用域和生命周期:
1、SqlSessionFactoryBuilder
:这个类可以被实例化、使用和丢弃,一旦创建了SqlSessionFactory
,就不再需要它了
2、SqlSessionFactory
:一旦被创建就应该在运行期间一直存在,没有理由丢弃它或重新创建另一个实例,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。
3、SqlSession
:每个线程都应该有自己的 SqlSession 实例,且 SqlSession 的实例是线程不安全的,不能被共享。
获取参数的两种方式:#{}和${}
#{}
的本质是:占位符赋值;${}
本质是字符串拼接。
所以我们尽可能的使用#{}
,而不是${}
。
当必须使用${}
时,注意需要加上单引号'${}'
。
特殊情况
- 模糊查询
解决方案:1、使用'%${xxx}%
。2、使用concat函数,例如:concat('%',#{xxx},'%')
。3、使用字符串拼接,例如:"%"#{xxx}"%"
- 动态设置表名
只能用${xxx}
字段名和属性名不一致问题
- 设置别名
select eid,emp_name AS empName,age,sex,email from t_emp;
- 通过全局配置
此时只能解决,例如emp_name
到empName
形式的自动映射
<setting name="mapUnderscoreToCamelCase" value="true"/>
- 通过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="selectEmps" resultMap="empResultMap">
select * from t_emp;
</select>
mybatis获取参数值的各种情况
1、mapper接口方法的参数为单个字面量时
- 此时用
#{}
或者${}
都可以,但是 ${}需要加单引号
2、mapper接口的方法参数有多个时
此时mybatis会自动将这些参数放入一个map中,以两种方式存储
- 以arg0、arg1…为键,以参数为值
- 以para1、param2…为键,以参数为值
3、mapper接口方法的参数有多个时,我们可以手动将参数存入map中,传入map参数
<!--mapper接口-->
User selectUserByMap(Map<String,String> map);
<!--mapper.xml-->
<select id="selectUserByMap" resultType="user">
select * from t_user where username = #{username} and password = #{password};
</select>
<!--测试类-->
Map<String,String> map = new HashMap<>();
map.put("username","zs");
map.put("password","1234");
4、mapper接口方法参数是一个实体类型
- 直接通过属性使用
#{属性}...
5、在mapper接口的方法中使用@Param("xxx")
来命名参数
User selectUserByParam(@Param(value ="username"));
使用:
- 以
#{xxx}
为键,以参数为值 - 以
#{param1}
、#{param2}
为键,以参数为值
综上,只有(4/5)两种情况
查询结果
查询结果为一条
可以用实体类对象、List集合、map接收
查询结果为多条
可以用List集合、map[]、List<Map<String,Object>>接收以及map和@MapKey(“主键”)结合使用,例如:
@MapKey("id")
Map<Integer, Object> selectUsersToMap(@Param("username")String username);
单行单列
int…
多对一或者一对一映射关系
通过级联属性赋值解决多对一问题
<resultMap id="empAndDeptResultMapOne" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="dept.did" column="did"></result>
<result property="dept.deptName" column="dept_name">
</resultMap>
通过
association
解决多对一问题
association: 处理多对一的映射关系
property: 需要处理多对的映射关系的属性名
javaType: 该属性的类型
<resultMap id="empAndDeptResultMapTwo" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<association property="dept" javaType="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name">
</association>
</resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptResultMapTwo">
select * from t_emp left join t_dept on t_emp.did = t_dept.did where eid = #{eid}
</select>
通过分步查询解决多对一问题(重点)
select
: 设置分步查询的sql的唯一标识(mapper接口的全类名.方法名)
column
: 设置分步查询的条件
fetchType
: 当开启了全局的延迟加载之后,可通过fetchType属性手动控制延迟加载的效果
fetchType=“eager|lazy”: 若没有开启全局的延迟加载,两个属性值都表示立即加载
<resultMap id="empAndDeptByStepResultMap" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<association property="dept"
select="com.herdgod.mapper.DeptMapper.getEmpAndDeptByStepTwo"
column="did"
fetchType="eager"></association>
</resultMap>
<select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">
select * from t_emp where eid = #{eid}
</select>
一对多映射关系
通过collection解决一对多问题
collection
: 处理一对多的映射关系
ofType
: 表示该属性所对应集合中存储数据的类型
<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>
</collection>
</resultMap>
<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>
通过分步查询处理一对多问题(重点)
select
: 设置分步查询的sql的唯一标识(mapper接口的全类名.方法名)
column
: 设置分步查询的条件
fetchType
: 当开启了全局的延迟加载之后,可通过fetchType属性手动控制延迟加载的效果
fetchType=“eager|lazy”: 若没有开启全局的延迟加载,两个属性值都表示立即加载
<resultMap id="deptAndEmpByStep" type="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<collection property="emps"
select="com.herdgod.mapper.EmpMapper.getDeptAndEmpByStepTwo"
column="did"></collection>
</resultMap>
<select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpByStep">
select * from t_dept where did = #{did}
</select>
多对多映射关系
1、通过创建一个新的类解决,不可取,这里不讲述(不推荐)
2、通过分步查询方式解决多对多映射关系(重点)
场景:一个用户有多个角色,一个角色可以有多个用户
业务:查询所有用户的所有角色信息
用户实体
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
//角色
private List<Role> roles;
}
角色实体
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Role {
private Integer rid;
private String roleName;
private String roleDesc;
}
用户接口
public interface UserMapper {
// 查询全部
List<User> selectAll();
}
角色接口
public interface RoleMapper {
/**
* 通过rid查询
*/
List<Role> findAllByRid(@Param("rid") Integer rid);
}
用户映射文件
<mapper namespace="com.herd.mapper.UserMapper">
<!--List<User> selectAll(); fetchType="lazy"-->
<resultMap id="manyMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<collection property="roles"
select="com.herd.mapper.RoleMapper.findAllByRid"
column="id"></collection>
</resultMap>
<select id="selectAll" resultMap="manyMap">
select * from user
</select>
</mapper>
角色映射文件
<mapper namespace="com.herd.mapper.RoleMapper">
<!--List<Role> findAllByRid(@Param("rid") Integer rid);-->
<resultMap id="map01" type="role">
<id column="rid" property="rid"></id>
<result property="roleName" column="role_name"></result>
<result property="roleDesc" column="role_desc"></result>
</resultMap>
<select id="findAllByRid" resultMap="map01" parameterType="int">
select * from role WHERE rid in (
SELECT rid from user_role where UID = #{rid}
)
</select>
</mapper>
测试:
@Test
public void testManyToMany(){
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.selectAll();
for (User user : users) {
System.out.println(user);
//System.out.println(user.getUsername());
}
}
结果:
如果我们只想插入所有的用户信息,不需要相关的角色信息时,我们可以使用延迟加载
延迟加载:就是在需要用到数据的时候才进行加载,不需要用到数据时就不加载数据
延迟加载开启:
在mybatis核心配置文件中设置
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
MyBatis插入之批处理
需求:批量新增一批数据
解决方式一:将批量新增的数据 放在List集合里面,遍历集合循环新增数据(不推荐)
@Test
public void test01(){
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = new ArrayList<>();
User user1 = new User("测试数据1","123",19,"男","123@qq.com");
User user2 = new User("测试数据2","123",19,"男","123@qq.com");
users.add(user2);
users.add(user1);
for (User user : users) {
mapper.insertUsers(user);
}
}
运行结果
由于insert语句多次执行,造成执行效率很低
解决方式二:开启批处理,使用批处理的方式进行数据的新增
开启批处理的方式有两种:
需要注意的是:这时需要手动提交事务!!
第一种:在mybatis的核心配置文件上开启
<settings>
<setting name="defaultExecutorType" value="BATCH"/>
</settings>
第二种:在创建SqlSession对象的时候,开启批处理
sqlSession = sessionFactory.openSession(ExecutorType.BATCH,false);
再次运行
添加后获取自增的主键
方式一:
keyProperty
: 实体类中的属性,对应数据库表中的自增主键
keyColumn
:数据库中表的自增的主键字段
<insert id="insertUser" parameterType="user">
<selectKey keyProperty="id" keyColumn="id" resultType="int">
select last_insert_id()
</selectKey>
insert into t_user values(null,#{username},#{password},#{age},#{sex},#{email})
</insert>
方式二:
useGeneratedKeys
: 设置当前标签中的sql使用了自增的主键
keyProperty
:将自增的主键的值 赋值给传输到实体类型中的属性
<insert id="insertUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(null,#{username},#{password},#{age},#{sex},#{email})
</insert>
动态SQL
缓存
Mybatis中的缓存机制,一般无法做到缓存同步,即数据库中的数据改变,无法察觉到
一级缓存
基于PerpetualCache的HashMap本地缓存,其存储作用域为Session级别,当Session进行flush或者close后,该Session中的所有Cache就将清空,默认打开一级缓存。
一级缓存失效的四种情况:
- 不同的SqlSession
- 同一个SqlSession,查询条件不同
- 手动清理了缓存,clearCache
- 同一个SqlSession两次查询条件相同,但是在中间执行了增删改语句
二级缓存
基于namespace和mapper的作用域起作用的,不是依赖于SqlSession,默认也是采用PerpetualCache,HashMap存储,需要单独开启。
步骤:
- 核心配置文件中
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
- mapper映射文件中
<!-- 开启二级缓存的支持 -->
<cache></cache>
- 二级缓存必须在SqlSession关闭或者提交之后有效
- 查询的数据所转换的实体类型必须实现序列化的接口
二级缓存失效的情况:
- 两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
二级缓存的相关配置:
在mapper配置文件中添加的cache标签可以设置一些属性:
- eviction属性:缓存回收策略
LRU:最近最少使用,溢出最长时间不被使用的对象(默认)
FIFO:先进先出,按对象进入缓存的顺序来移除它们
SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
WEAK:弱引用,更积极地移除基于垃圾回收器状态和弱引用规则的对象 - flushInterval属性:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新时间间隔,缓存仅仅调用语句时刷新 - size属性:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出 - readOnly属性:只读,true/false
true:只读缓存,会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改,提供了很重要的性能优势
false:读写缓存,会返回缓存对象的拷贝(通过序列化)。效率低一些,但是安全,默认false。
缓存查询顺序:
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- SqlSession关闭后,一级缓存中的数据会写入二级缓存
整合第三方缓存:Ehcache(了解)
逆向工程
- 引入依赖和插件
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.7</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.9</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.mchange</groupId>-->
<!-- <artifactId>c3p0</artifactId>-->
<!-- <version>0.9.5.2</version>-->
<!-- </dependency>-->
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
- 创建mybatis的核心配置文件:略
- 创建逆向工程的配置文件
注意:逆向工程的文件名必须是:generatorConfig.xml
简洁(丐)版:targetRuntime="MyBatis3Simple
高配版:targetRuntime="MyBatis3
区别:
简洁版:只有简单的crud,高配版:除了简单的crud还有模糊查找、in()等
都只能生成单表操作,不能处理多表关系
<?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.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/demo"
userId="root"
password="wr990916">
</jdbcConnection>
<!-- javaBean的生成策略-->
<javaModelGenerator targetPackage="com.herd.pojo"
targetProject=".\src\main\java">
<!--
是否生成子包。如果为true com.herd.pojo生成的保姆那个带有层级
目录
false com.herd.pojo就是一个包名
-->
<property name="enableSubPackages" value="true" />
<!--
通过数据表字段生成pojo。如果字段名称带空格,会去掉空格
-->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="com.herd.mapper"
targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.herd.mapper"
targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写
domainObjectName -->
<!-- domainObjectName属性指定生成出来的实体类的类名 -->
<table tableName="t_user" domainObjectName="User"/>
</context>
</generatorConfiguration>
生成后的项目结构:
分页插件
引入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.1</version>
</dependency>
在MyBatis核心配置文件中配置插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
查询相关常用数据:
- pageNum:当前页的页码
- pageSize:每页显示的条数
- size:当前页显示的真实条数
- total:总记录数
- pages:总页码数
- prePage:上一页的页码
- nextPage:下一页的页码
- isFirstPage/isLastPage:是否为第一页/最后一页
- hasPreviousPage/hasNextPage:是否存在上一页/下一页
- navigatePages:导航分页的页码数
- navigatepageNums:导航分页的页码,[1,2,3,4,5]
另
MyBatis执行流程
1.加载MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件
2.构造会话工厂SqlSession
3.会话工厂创建SqlSession对象(包含了执行SQL语句的所有方法)
4.操作数据库的接口,Executor执行器,同时负责查询缓存的维护
5.Executor接口的执行方法中有一个MapperStatement类型的参数,封装了映射信息
6.输入参数映射
7.输出结果映射
延迟加载的底层原理
1.使用CGLIB创建目标对象的代理对象
2.当调用目标方法时,进入拦截器invoke方法,发现目标方法是null值,执行sql查询
3.获取数据以后,调用set方法设置属性值,再继续查询目标方法,就有值了
MyBatis核心配置文件
<configuration>
<!--引入properties文件-->
<properties resource="jdbc.properties"></properties>
<!--设置别名-->
<typeAliases>
<!--默认时类名,不区分大小写-->
<typeAlias type="com.herdgod.pojo.User" alias="user"></typeAlias>
<!--常用: 表示把pojo包下的所有都设置别名为 类名 小写-->
<!--<package name="com.herdgod.pojo"/>-->
</typeAliases>
<!--environments: 配置mybatis的环境-->
<environments default="development">
<!--environment: 配置某个具体的环境
属性: id:表示连接数据库的环境唯一标识,不能重复-->
<environment id="development">
<!--transactionManager:设置事务的管理方式
属性:JDBC|MANAGED
JDBC:表示当前环境中,执行sql时,使用的是JDBC中原生的事务管理方式,事务的提交回滚需要手动控制
MANAGED:被管理,例如Spring,意思就是没什么卵用-->
<transactionManager type="JDBC"/>
<!--dataSource:配置数据源
属性:
type: 设置数据源的类型
POOLED: 表示使用数据库连接池缓存数据库连接
UNPOOLED:表示不适用数据库连接池
JNDI: 表示使用上下文中的数据源-->
<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>
<mapper resource="com/herdgod/mapper/UserMapper.xml"></mapper>
<!--<mapper class="com.herdgod.mapper.UserMapper"></mapper> 不常用-->
<!--以包为单位引入映射文件
要求:
1、mapper接口所在的包要和映射文件所在的包一致
2、mapper接口要和映射文件的名字一致
创建多层包时需要用/分隔 com/herdgod/mapper -->
<!--<package name="com.herdgod.mapper"/>-->
</mappers>
</configuration>
映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--mybatis面向接口编程的两个一致
1、映射文件的namespace和mapper接口的全类名一致
2、映射文件中SQL语句的id和mapper接口中的方法名一致
-->
<mapper namespace=".....">
</mapper>