文档说明
此文档为拉钩Java高薪训练营2期课程学习过程,记录的文档。顺便说一句拉钩的课程,整个课程,体系非常全,价格上也是所有培训课程中最便宜的。如果希望构建一个整体的技术视野,非常推荐。
整体架构
SQL 执行流程
配置文件
SqlMapConfig.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>
<!--加载外部的properties文件-->
<properties resource="jdbc.properties"></properties>
<!--开启二级缓存 -->
<!-- <settings>
<setting name="cacheEnabled" value="true"/>
</settings> -->
<!--给实体类的全限定类名给别名-->
<typeAliases>
<!--批量起别名:该包下所有的类的本身的类名:别名还不区分大小写-->
<package name="com.beau.pojo"/>
</typeAliases>
<plugins>
<!-- 翻页插件配置 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
</plugin>
<!-- 通用 Mapper 配置 -->
<plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
<!--指定当前通用mapper接口使用的是哪一个-->
<property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
</plugin>
</plugins>
<environments default="development">
<environment id="development">
<!--当前事务交由JDBC进行管理-->
<transactionManager type="JDBC"></transactionManager>
<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="com.beau.mapper"/>
</mappers>
</configuration>
mapper.xml
Mapper 动态代理开发规范
- Mapper.xml 文件中的 namespace 与 mapper 接口的类路径相同。
- Mapper 接口方法名和 Mapper.xml 中定义的每个statement的id相同
- Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同
- Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同
- Mapper.xml 文件存放位置和接口存放位置包名相同,可以在 resource 下面创建同名包存放 mapper 文件。(此规则非强制,只要能扫描并到就可以了)
基本增删改查
<?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.lagou.dao.IUserDao">
<!--namespace : 名称空间:与id组成sql的唯一标识
resultType: 表明返回值类型-->
<!--抽取sql片段-->
<sql id="selectUser">
select * from user
</sql>
<!--查询用户-->
<select id="findAll" resultType="uSeR">
<include refid="selectUser"></include>
</select>
<!--添加用户-->
<!--parameterType:参数类型-->
<insert id="saveUser" parameterType="user" >
insert into user values(#{id},#{username})
</insert>
<!--修改-->
<update id="updateUser" parameterType="user">
update user set username = #{username} where id = #{id}
</update>
<!--删除-->
<delete id="deleteUser" parameterType="int">
delete from user where id = #{abc}
</delete>
</mapper>
动态SQL
<!--多条件组合查询:演示if-->
<select id="findByCondition" parameterType="user" resultType="user">
<include refid="selectUser"></include>
<where>
<if test="id !=null">
and id = #{id}
</if>
<if test="username !=null">
and username = #{username}
</if>
</where>
</select>
<!--多值查询:演示foreach-->
<select id="findByIds" parameterType="list" resultType="user">
<include refid="selectUser"></include>
<where>
<foreach collection="array" open="id in (" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
关联查询
一对一查询
如,在用户和订单中,订单对用户,就是一对一。
在 POJO 中,用 User 对象,表示关联关系
private User user;
IOrderMappper.xml ```xml
<a name="wfiTR"></a>
#### 一对多查询
如,在用户和订单的关系中,用户对订单,其实就是一对多的关系。
1. 在 POJO 中用集合接收关联对象
```java
private List<Order> orderList
- IUserMapper.xml
```xml
<a name="GB2e0"></a>
#### 多对多查询
多对多,其实就是多个一对多。需要在两个关联的 POJO 类中,都用集合接收关联的结果。
<a name="4nnkC"></a>
# 注解开发
1. `@Insert`
1. `@Update`
1. `@Delete`
1. `@Select`
1. `@Result` 实现结果集封装
1. `@Results` 可以与 `@Result` 一起使用,封装多个结果集
1. `@One` 实现一对一结果集封装
1. `@Many` 实现一对多结果集封装
![image.png](https://cdn.nlark.com/yuque/0/2020/png/794563/1583712551681-b0668532-6d2e-48eb-8805-ea4cd4f5488b.png#align=left&display=inline&height=219&margin=%5Bobject%20Object%5D&name=image.png&originHeight=437&originWidth=1230&size=144928&status=done&style=none&width=615)
<a name="kcc8P"></a>
## 一对一查询
使用注解开发,通过订单查询用户,在 `@one` 注解中,通过 `select` 指定调用了 `UserMapper` 中的 `findById` 方法。注意 ① 的部分, `prpperty` 指定的是在 `OrderPOJO` 中, `User` 所对应的属性,而 `column` 则是指定的通过 `select * from orders` 这条语句查询出来的关联用户的列名。查询出来的每个 `uid` 都会调用 `select * from user where id=#{id}` 将结果集返回。
```java
public interface OrderMapper {
@Select("select * from orders")
@Results({
@Result(id=true,property = "id",column = "id"),
@Result(property = "ordertime",column = "ordertime"),
@Result(property = "total",column = "total"),
@Result(property = "user",column = "uid", ①
javaType = User.class,one = @One(select ="com.lagou.mapper.UserMapper.findById"))
})
List<Order> findAll();
}
public interface UserMapper {
@Select("select * from user where id=#{id}")
User findById(int id);
}
一对多查询
public interface UserMapper {
@Select("select * from user")
@Results({
@Result(id = true,property = "id",column = "id"),
@Result(property = "username",column = "username"),
@Result(property = "password",column = "password"),
@Result(property = "birthday",column = "birthday"),
@Result(property = "orderList",column = "id",
javaType = List.class,many = @Many(select ="com.lagou.mapper.OrderMapper.findByUid"))
})
List<User> findAllUserAndOrder();
}
public interface OrderMapper {
@Select("select * from orders where uid=#{uid}")
List<Order> findByUid(int uid);
}
高级
缓存
- 缓存分为一级缓存和二级缓存。
- 一级缓存是 SqlSession 级别,内部结构是 HashMap,不同的 SqlSession 中的缓存互不干扰。当发生 CUD 时,缓存的数据会失效。一级缓存默认是开启的。
- 二级缓存是 namespace(mapper) 级别的,当 namespace 中出现 CUD 时,缓存数据会失效。二级缓存在多表的情况下,可能会出现脏读(另一个 Mapper 中更新了当前 Mapper 中关联查询的对象,当前 Mapper 中的缓存并不会失效),因此不推荐使用。
- 如果要自己实现二级缓存,需要实现
Cache
接口。POJO 类也需要实现序列化接口。官方也有提供 redis 版的二级缓存实现。插件
插件机制
MyBatis 插件本质上是拦截器。可以拦截如下方法:
- 执行器 Executor 中的 updata query commit rollbak 等方法。
- SQL 语法构建器 StatementHandler 中的 prepare parametrize batch update query 等方法。
- 参数处理器 ParameterHandler 中的 getParameterObject setParameters 等方法。
- 结果集处理器 ResultSetHandler 中的 handleResultsSets handleOutputParameters 等方法。
MyBatis 插件在拦截上面方法时,会对其进行增强,生成相应的代理对象。
自定义插件
- 实现 Inteceptor 接口
@Intercepts({
@Signature(type= StatementHandler.class, // 指定拦截的方法
method = "prepare",
args = {Connection.class,Integer.class})
})
public class MyPlugin implements Interceptor {
/*
拦截方法:只要被拦截的目标对象的目标方法被执行时,每次都会执行intercept方法
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("对方法进行了增强....");
return invocation.proceed(); //原方法执行
}
/*
主要为了把当前的拦截器生成代理存到拦截器链中
*/
@Override
public Object plugin(Object target) {
Object wrap = Plugin.wrap(target, this);
return wrap;
}
/*
获取配置文件的参数
*/
@Override
public void setProperties(Properties properties) {
System.out.println("获取到的配置文件的参数是:"+properties);
}
}
- 在 sqlMapConfig.xml 加载插件
<plugins>
<plugin interceptor="com.lagou.plugin.MySqlPagingPlugin">
<!-- 参数配置 -->
<property name="name" value="Bob"/>
</plugin>
</plugins>
分页插件
- 导入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>3.7.5</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>0.9.1</version>
</dependency>
- 在 sqlMapConfig.xml 中配置加载插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
- 使用
@Test
public void pageHelperTest(){
PageHelper.startPage(1,1);
List<User> users = userMapper.selectUser();
for (User user : users) {
System.out.println(user);
}
PageInfo<User> pageInfo = new PageInfo<>(users);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页显示的条数:"+pageInfo.getPageSize());
}
通用 Mapper 插件
:::info 如果同时使用分页插件和通用 Mapper 插件,一定要把分页插件配置在通用 Mapper 插件之前。 :::
- 导入依赖
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>3.1.2</version>
</dependency>
- sqlMapConfig.xml 中加载插件
<plugins>
<plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
<!--指定当前通用mapper接口使用的是哪一个-->
<property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
</plugin>
</plugins>
- 使用
@Test
public void mapperTest() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setId(1);
User user1 = mapper.selectOne(user);
System.out.println(user1);
//2.example方法
Example example = new Example(User.class);
example.createCriteria().andEqualTo("id",1);
List<User> users = mapper.selectByExample(example);
for (User user2 : users) {
System.out.println(user2);
}
}
经典问题
Mybatis延迟加载原理
- MyBatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一查询,collection 指的是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载
lazyLoadingEnabled=true|false
。 MyBatis 延迟加载原理:使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器 invoke() 方法发现 a.getB() 是null值,那么就会单独发送事先保存好的查询关联B对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName() 方法的调用。
Mybatis都有哪些Executor执行器
SimpleExecutor:每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象。
- ReuseExecutor:执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map 内,供下一次使用。简言之,就是重复使用Statement 对象。
- BatchExecutor:执行 update(没有 select,JDBC批处理不支持 select),将所有 sql 都添加到批处理中(addBatch()),等待统一执行( executeBatch() ),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch() 完毕后,等待逐一执行 executeBatch() 批处理。与 JDBC 批处理相同。
核心接口
- Configuration
- MappedStatement
- SqlSession
- SqlSessionFactory
- Executor