复习笔记3-mybatis

本文详细介绍了MyBatis的ORM映射、动态SQL、延迟加载、分页策略、主键生成策略等内容,包括Mapper接口的工作原理、物理与逻辑分页的区别、动态SQL标签的使用以及批量插入的实现方式。此外,还探讨了MyBatis的一级缓存、二级缓存及其配置,以及MyBatis与MyBatis-Plus的差异。
摘要由CSDN通过智能技术生成

ORM

ORM(Object Relational Mapping),对象关系映射,就是通过使用描述 对象 和 数据库 之间映射的元数据,将程序中的对象自动持久化到数据库中。

动态代理在 mybatis 框架的应用

1、普通 mapper 接口的代理是 jdk 代理
2、懒加载的代理是 cglib

@MapperScan 和 @Mapper 区别 (针对接口的)

@MapperScan(“com.winter.dao”)
启动类上加该注解以后,com.winter.dao 包下面的【所有接口】,在编译之后都会生成相应的实现类

@Mapper 注解:
在接口类上添加了@Mapper,在编译之后会生成相应的接口实现类,有些情况没用@MapperScan,而用了@Mapper后无法注入,可能是 jar 包冲突了

扫描 xml 文件

对于Maven项目,IntelliJ IDEA 默认是不处理 src/main/java 中的非 java 文件的,所以将 mapper.xml 放在 src/main/resources 中比较合适。

#mybatis-plus 配置mapper xml文件的路径(扫描mapper文件下的xml)
mybatis-plus.mapper-locations=classpath:com/xx/xx/mapper/xml/*.xml

<! 或者 mybatis-config.xml 指定mapper.xml所在位置-->
<property name="mapperLocations" value="classpath:mapper/*Mapper.xml" />

mybatis初始化过程

参考 https://zhuanlan.zhihu.com/p/97879019 mybatis运行原理
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

1.项目启动时解析 mybatis 主配置文件,包括数据源及 mapper.xml文件路径等封装到 Configuration 对象中。
2.通过 XmlConfigBuilder 把路径下所有的 mapper.xml 解析出来,把每个 xml 中的每个增删改查的 sql 标签
  解析成一个个 MapperStatement 并把解析出来的这些对象(属性包括这个 sql 标签的 id ,sql 语句,入参,出参等等)装到Configuration 的 Map 中备用。
3.根据 mapper.xml 中的名称空间 namespace 再反射代理,在 Configuration 中维护一个Map对象,key 值是 【mapper 接口对象】,
  value 值是通过动态代理生产的 class 该接口的【代理对象】

Mybatis是半自动ORM映射工具,与全自动的区别

Hibernate属于全自动 ORM 映射工具,查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。
而Mybatis在查询关联对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。

mybatis原理浅析
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Mybatis 延迟加载和原理

Mybatis 仅支持一对一和一对多查询时的延迟加载
1.在Mybatis配置文件中,可以配置是否启用延迟加载 LazyLoadingEnabled=true|false。
2.他的原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()
方法发现 a.getB()是null值,那么就会单独发送实现保存好的查询关联B对象的SQL,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成 a.getB().getName()方法的调用,这就是延迟加载的基本原理。

mybatis 中的 # 和 $

1. #{}是预编译处理,防止SQL注入, ${}是字符串替换,一般模糊查询用 like '%${value}%' ,其他情况用#{}
2. mybatis在处理#{}时,会将sql中的#{}替换为?号,调用 PreparedStatement 的set方法来赋值;mybatis在处理 $ {} 时,就是替换成变量的值。

Xml 映射文件中的标签有哪些

select|insert|update|delete ,<resultMap><parameterMap><sql><include><selectKey>,
加上动态 sql 的 9 个标签,trim|where|set|foreach|if|choose|when|otherwise|bind等,
<include> 标签引入公共 sql 片段如 select * from test
<selectKey> 为不支持自增的主键生成策略标签。注意和@SelectKey区别
<association> 为关联查询时,resultMap 定义一对一用 
<collection> 为关联查询时,resultMap 定义一对多用
<resultMap type="account" id="accountMap">
	<id column="aid" property="id"/>
	<result column="uid" property="uid"/>
	<result column="money" property="money"/>
	<!-- 它是用于指定从表方的引用实体属性的 -->
	<association property="user" javaType="user">
		<id column="id" property="id"/>
		<result column="username" property="username"/>
	</association>
</resultMap>

注解替代xml文件中的标签

@Insert: 实现新增
@Update: 实现更新
@Delete: 实现删除
@Select: 实现查询
@Result: 实现结果集封装替代 <result property="roleName" column="role_name"></result>
@Results: 代替的是标签 <resultMap>,可以与 @Result 一起使用,封装多个结果集
@ResultMap: 实现引用 @Results 定义的封装
@One: 实现一对一结果集封装,代替 <assocation> ,fetchType 会覆盖全局的配置参数 lazyLoadingEnabled
@Many: 实现一对多结果集封装,代替了<Collection> 标签
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace: 实现注解二级缓存的使用

@SelectKey 注解 和 <selectKey >标签

两种功能一样,注解是用在 mapper 接口的方法上,标签是用在 mapper.xml 的 sql 里,都是返回插入记录的主键值

返回插入记录的主键
(1)oracle 中数据库主键用序列列,需要先预先生成,order 的值就为 before
(2)mysql 中如果主键用自增,插入之后才能获知,order 的值就为 after 
这两种情况都可以通过 SelectKey 解决,第一个种就是 before,第二张是 after 。

注解中主键生成先后顺序用 before=false 或者 true 来表示

@Insert("insert into user(username,birthday,sex,address) values (#{userName},#{userBirthday},#{userSex},#{userAddress})")
@SelectKey(keyColumn = "id",keyProperty = "userId",before = false,resultType =Integer.class,statement = {" select last_insert_id()"})
@ResultMap("userMap")
void saveUser(User user);

xml 中用 order 的属性来表示主键生成的先后顺序,功能都是一样的

<insert id="insert" parameterType="SysUser" >
 <!-- 将最后插入表中自增 id 查询出来赋值给实体类中id属性 -->
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Long">
 SELECT LAST_INSERT_ID() as id
</selectKey>
	insert into sys_user (id,  name, email, phone)values (#{id},#{name},#{email},#{phone})
</insert>

Mapper接口的工作原理是什么?参数不同时,方法能重载吗

Mapper接口的全限名,就是映射文件中的 namespace 的值,接口的方法名,就是映射文件中 MappedStatement
的 id 值,接口方法内的参数,就是传递给 sql 的参数。Mapper 接口是没有实现类的,当调用接口方法时,接口
全限名+方法名拼接字符串作为 key 值,可唯一定位一个MappedStatement,
举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到 namespace 为com.mybatis3.mappers.StudentDao
下面id = findStudentById的MappedStatement。在 MyBatis 中,每一个<select><insert><update>
<delete>标签,都会被解析为一个MappedStatement对象。

Dao(Mapper) 接口里的方法可以重载,但是 Mybatis 的 XML 里面的 ID 不允许重复。

Dao(Mapper) 接口的工作原理是 JDK 动态代理,MyBatis 运行时会使用 JDK 动态代理为 Dao 接口生成代理
 proxy 对象,代理对象会拦截接口方法,转而执行 MappedStatement 所代表的 sql,后将 sql 执行结果返回

MyBatis 不同的 Xml 映射文件,id 是否可以重复

namespace+id 是作为 Map<String, MappedStatement>的 key 使用的,但是 namespace 不是必须的,
如果配置了 namespace, id 可以重复;如果没有配置 namespace,那么 id 不能重复

物理分页和逻辑分页

1.物理分页
如 MySQL 数据库提供了 limit 关键字,数据库返回的就是分页结果。

2.逻辑分页
逻辑分页依赖的是程序员编写的代码。数据库返回的是全部数据,然后再由程序员通过代码获取分页数据,常用的操作是一次性从数据库中查询出全部数据并存储到 List 集合中,因为List集合有序,再根据索引获取指定范围的数据。

MyBatis 的分页

1.物理分页:使用 pageHelper 分页插件,原理是拦截待执行的 sql,根据 dialect 方言,添加对应的分页语句和参数,然后重写 sql 执行
2.逻辑分页:使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页
 		List<Book> list = mapper.selectBookByName(map, new RowBounds(0, 5));

mybatis 批量插入

1)for 循环调用 Dao 中的单条插入方法,耗时每次要拿 session,可返回操作结果(0/1)

2)传一个 List 参数,使用 Mybatis 的 foreach 批量插入,实际是拼接成一个 sql,省时,也可返回操作结果(0/1),但如果有一条失败,会回滚全部,mybatis本省不限制sql长度,但是数据库会限制,mysql限制长度为 4M,超长会失败,推荐分批,且对参数集合长度为校验,如果为 0,也会执行报错

<insert id="addUser" parameterType="java.util.List" >
	insert into user(name,age) values
    <foreach collection="list" item="item" index="index" separator=",">
        (#{item.name},#{item.age})
    </foreach>
</insert>

3)mybatis批量插入的另外一种不推荐的写法

<insert id="addBatchUser" parameterType="java.util.List" >
    <foreach collection="list" item="item" index="index" separator=";">
        insert into user(name,age) values(#{item.name},#{item.age})
    </foreach>
</insert>

这种写法也能实现批量插入。但明显字符长度增加,浪费了长度,且这种 foreach 拼接成的 sql 语句,是以分号“;” 分隔的多条 insert 语句。相当于单条单条执行并提交,中途数据报错,前面不会回滚,要回滚,需手动抛出异常(spring 事务需要运行时异常才会回滚事务)

4)实际 mybatis 还提供 ExecutorType.BATCH 批量插入(几十万上百万推荐)

/**
 * session使用批量模式插入数据
 */
@Test
public void testInsertBatch2()  {
    long start = System.currentTimeMillis();
    //跟上述sql区别,将session设置为批量形式
    SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory()
            .openSession(ExecutorType.BATCH, false);
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    for (int i = 0; i < 10000; i++) {
        User user = User.builder().age(i).name("zhangsan" + i).password(i + "xxxx").sex("男").build();
        int insert = mapper.insert(user);
        System.out.println(insert);
    }
    sqlSession.commit();
    long end = System.currentTimeMillis();
    System.out.println("----时间1535-----------" + (start - end) + "---------------");
}

<insert id="insert">
	INSERT INTO user ( name,password,sex,age) VALUES( #{name}, #{password}, #{sex}, #{age})
</insert>

mybatis-plus 的 savebatch 就是分批插入,默认 1000 条一批
如果 mybatis-plus 的批量保存太慢,针对 mysql 可以加 rewriteBatchedStatements 参数来提高

MyBatis 执行批量插入,能否返回数据库主键列表

能,JDBC 都能,MyBatis 当然也能。

MyBatis 动态sql 及原理

MyBatis 提供了 9 种动态 sql 标签 trim|where|set|foreach|if|choose|when|otherwise|bind。
其执行原理为,使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。
遍历标签<foreach>
<if test="roleNameList!=null and roleNameList.size()&gt; 0">
	<foreach collection="roleNameList" item="rolename" separator="," open="and rolename in(" close=")">
    	#{rolename,jdbcType=VARCHAR}
	</foreach>
</if>
collection 标识我们程序传值过来的集合
open 表示我们遍历的集合以什么字符开始
close 表示我们遍历的集合以什么字符结尾
item 是给我们集合遍历取一个变量 
separator 表示的是分隔符,将我们集合中遍历出来的数据用","分隔开。

MyBatis 是如何将 sql 执行结果封装为目标对象并返回的?都有哪些映射形式

1.使用 <resultMap> 标签,逐一定义列名和对象属性名之间的映射关系。
2.第二种是使用 sql 列的别名功能,将列别名书写为对象属性名,比如 T_NAME AS NAME,对象属性名一般是 name,小写,但是列名不区分大小写,MyBatis 会忽略列名大小写,智能找到与之对应对象属性名,你甚至可以写成 T_NAME AS NaMe,MyBatis 一样可以正常工作。

有了列名与属性名的映射关系后,MyBatis 通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。

模糊查询 like 语句

(1)’%${question}%’ 可能引起SQL注入,不推荐
(2)“%”#{question}“%” 注意:因为#{…}解析成sql语句时候,会在变量外侧自动加单引号’ ',所以这里 % 需要使用双引号" ",不能使用单引号 ’ ',不然会查不到任何结果。
(3)CONCAT(’%’,#{question},’%’) 使用CONCAT()函数,推荐
(4)使用bind标签

mapper 中传递多个参数

  1. 顺序传参法(不建议sql层表达不直观,且一旦顺序调整容易出错)
public User selectUser(String name, int deptId);

<select id="selectUser" resultMap="UserResultMap">
    select * from user
    where user_name = #{0} and dept_id = #{1}
</select>
  1. @Param注解传参法
public User selectUser(@Param("userName") String name, int @Param("deptId") deptId);
  1. Map传参法(#{}里面的名称对应的是 Map 里面的 key 名称)
public User selectUser(Map<String, Object> params);

<select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>

4.Java Bean传参法

public User selectUser(User user);

<select id="selectUser" parameterType="com.jourwon.pojo.User" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>

MyBatis 中接口绑定方式

1.通过注解绑定,在接口的方法上面加上 @Select @Update等注解里面包含Sql语句来绑定(Sql简单时推荐)
2.通过 xml 里面写SQL来绑定, 指定xml映射文件里面的 namespace 必须为接口的全路径名(SQL复杂时推荐)

MyBatis 延迟加载或延迟加载

在表关联查询时,按设置延迟规则推迟对关联对象的 select 查询。如一对多查询的时,只查询出一方,当程序中需要多方的数据时,mybatis 再发出 sql 语句进行查询,减少数据库压力。延迟加载只是对关联对象的查询有迟延设置,对主加载对象是直接执行查询。

就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。关联查询,一对一和一对多即
association、collection 具备延迟加载功能,
在 Mybatis 的配置文件 SqlMapConfig.xml 文件中添加延迟加载的配置
<settings>
	<setting name="lazyLoadingEnabled" value="true"/>
	<setting name="aggressiveLazyLoading" value="false"/>
</settings>

注解式:one=@One(select="com.itheima.dao.IUserDao.findById",fetchType=FetchType.LAZY)

Mybatis 中一级缓存与二级缓存的区别

1.默认开启一级缓存。一级缓存在操作数据库时需要构造 sqlSession 对象,相当于 request 级别的
在这里插入图片描述

一个service方法中包括很多 mapper 方法调用。
service{
	//开始执行时,开启事务,创建 SqlSession 对象
	//第一次调用mapper的方法 findUserById(1)
	//第二次调用mapper的方法 findUserById(1), 从一级缓存中取数据
	//aop控制 只要方法结束,sqlSession 关闭 sqlsession 关闭后就销毁数据结构,清空缓存
	Service 结束 sqlsession 关闭
}

如果是执行两次 service 调用查询相同的用户信息,不走一级缓存,因为 service 方法结束,sqlSession 就关闭,一级缓存就清空。

2.MyBatis 的二级缓存是 mapper 级别的缓存跨 sqlsession ,它可以提高对数据库查询的效率。多个 SqlSession 去操作同一个 Mapper的 sql 语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。二级缓存的 POJO 类需要实现 Serializable 接口,二级缓存适合对访问多且对查询结果实时性要求不高的场景

mybatis 和 mbatis-Plus 区别

参考 https://www.jianshu.com/p/ceb1df475021 mybatis-plus入门
https://www.jianshu.com/p/a4d5d310daf8 mybatis-plus进阶

mbatis-plus 是一个 mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,其实就是它已经封装好了一些 crud 方法,我们不需要再写 xml 了,直接调用简化开发,就类似于JPA

基于 mybatis:需编写 EmployeeMapper 接口,并在 EmployeeMapper.xml 映射文件中手动编写 CRUD 方法对应的sql语句。MyBatis 逆向工程的代码生成器基于 xml 配置文件进行生成,可生成: 实体类、Mapper 接口、Mapper 映射文件。
基于 mbatis-Plus:只需要创建 EmployeeMapper 接口, 并继承 BaseMapper 接口。多表查询还是要建 xml 文件写sql或者基于注解写sql,MP 的代码生成器基于 Java 代码进行生成,可生成: 实体类(可以选择是否支持 AR)、Mapper 接口 、Mapper 映射文件、 Service 层、 Controller 层。

mbatis-Plus的复杂查询实例

mbatis-Plus 几种复杂条件查询(EntityWrapper,还有Condition)如下

List<Employee> employees = emplopyeeDao.selectPage(new Page<Employee>(1,3),
     new EntityWrapper<Employee>()
        .between("age",18,50)
        .eq("gender",0)
        .eq("last_name","tom")
);

 List<Employee> employees = emplopyeeDao.selectPage(
                new Page<Employee>(1,2),
                Condition.create()
                        .between("age",18,50)
                        .eq("gender","0")
 );

 void testSelectWrapper() {
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("name", "helen");
        List list = userMapper.selectList(queryWrapper);
        list.forEach(System.out::println);
    }

mybatis可以物理分页和逻辑分页

物理分页与逻辑分页

一 概述
1.物理分页: 物理分页依赖的是某一物理实体,这个物理实体就是数据库,比如 MySQL 数据库提供了 limit 关键字,程序员只需要编写带有 limit 关键字的 SQL 语句,数据库返回的就是分页结果。
2.逻辑分页: 逻辑分页一般是一次性从数据库中查询出全部数据并存储到 List 集合中,因为 List 集合有序,再根据索引获取指定范围的数据。依赖的是程序员编写的代码。数据库返回的不是分页结果,而是全部数据
二 对比
1.数据库负担: 物理分页每次都访问数据库,逻辑分页只访问一次数据库,物理分页对数据库造成的负担大。
2.服务器负担: 逻辑分页一次性将数据读取到内存,占用了较大的内容空间,物理分页每次只读取一部分数据,占用内存空间较小。
3.实时性: 逻辑分页一次性将数据读取到内存,数据发生改变,数据库的最新状态不能实时反映到操作中,实时性差。物理分页每次需要数据时都访问数据库,能够获取数据库的最新状态,实时性强。
4.适用场合: 逻辑分页主要用于数据量不大、数据稳定的场合,物理分页主要用于数据量较大、更新频繁的场合。

mybatis无法将null值插入

解决方法:

一、指定插入值得jdbcType,将sql改成

insert into user(id,name) values(#{id,jdbcType=VARCHAR},#{name,jdbcType=VARCHAR}) 

二、在mybatis-config.xml文件中配置一下,添加settings配置,如下:(推荐)

<configuration>
......
<settings>
    <setting name="jdbcTypeForNull" value="NULL" />
</settings>
......
</configuration>

不为空才更新

<update id="updateStudent_if_set" parameterType="liming.student.manager.data.model.StudentEntity">  
    UPDATE STUDENT_TBL  
    <set>  
        <if test="studentName != null and studentName != '' ">  
            STUDENT_TBL.STUDENT_NAME = #{studentName},  
        </if>  
        <if test="studentSex != null and studentSex != '' ">  
            STUDENT_TBL.STUDENT_SEX = #{studentSex},  
        </if>  
    </set>  
    WHERE STUDENT_TBL.STUDENT_ID = #{studentId};      
</update>  

mybatis 主键策略 id_type

AUTO	数据库 ID 自增
NONE	无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUTINPUT	insert 前自行 set 主键值
ASSIGN_ID	分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
ASSIGN_UUID	分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认 default 方法)

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值