Mybatis学习笔记

1.mybaties分页

com.github.pagehelper.PageHelper

github上的一个基于mybatis的分页插件PageHelper

PageHelper.startPage(currentPage, pageSize);

原理:

1.1把计算好的分页放到线程变量中

1.2在mybatis查询,会进入到拦截器中,根据这个线程变量看是否要分页

PageHelper实现分页原理 - 十黎九夏 - 博客园

1.3多数据源适配分页(关于auto-dialect: true是网上看到的  没有亲自尝试)

pagehelper:
#  helperDialect: mysql
  reasonable: false
  supportMethodsArguments: true
  params: count=countSql
# 默认false,当为true时,自动检验适合的数据库
  auto-dialect: true
   # 这个一定要加上,不然mysql和oracle分页两个只能用一个,另一个会报错,加上后,两中数据库分页都可以用了
  auto-runtime-dialect: true

2.PageHelper的reasonable属性

pagehelper.reasonable=true

参考文章

使用Pagehelper进行分页时下一页没有数据依旧有数据返回_花儿小新♥-CSDN博客

当reasonable:true时在pageNum<1会查询第一页,如果pageNum>pages会查询最后一页

也就是说当pageNum>你的最大页数时会返回最后一页的数据而不是返回null

禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据

3.mybatis传参数需要用@Param注释参数

    int deleteByIds(@Param("idList") List<String> idList);

之前我没加@Param  报了500 说找不到idList参数

如果是collection,默认是collection="list"

4.mybatis中的foreach标签中,collection属性怎么写

接上边那一点

参考

MyBatis(映射文件中使用foreach标签时报错,属性collection的问题)

MyBatis,在foreach标签对中,使用List类型参数时,collection属性到底怎么写_HairyCoder的博客-CSDN博客

默认collection='list' 但是如果mapper中使用了@Param("") ,collection就得按照@Param指定的值

//dao层接口伪代码
public interface UserMapper{

	//这个方法的参数没有使用@Param注解,在foreach标签中,collection=“list”
	int updateByUserIds(List<Integer> userIds);
	
	//这个参数使用了@Param注解,在foreach标签中,collection=“userIds”
	//如果不加@Param注解指定参数名,而直接这样用,xml文件会报错
	int modifyByUserIds(@Param("userIds") List<Integer> userIds)
}

5.resultMap与resultType的区别

5.1 resultMap

5.1.1resultMap是xml中定义的<resultMap>标签的id

 <resultMap id="BaseResultMap" type="com.wish.biz.pf.productfactory.model.baseentity.Pf1001" >
  <select id="selectAttrListByTemplateId" parameterType="String" resultMap="BaseResultMap" >

5.1.2resultType需要写返回类的全路径

 <select id="countByExample" parameterType="com.wish.biz.pf.Pf1001Example" resultType="java.lang.Integer" >

5.2resultType

 5.2.1resultType对应pojo与数据库字段应一一对应

5.2.2 如果数据库中有下划线分割

如数据库字段为user_name,自定义实体属性为userName,可以在application.yml中开启驼峰自动转换

mybatis:
  configuration:
    map-underscore-to-camel-case: true  # 开启驼峰自动转换

或者application.properties中

mybatis.configuration.map-underscore-to-camel-case=true

5.3 使用场景

5.3.1目前,在我的实际应用中,一般java提供的类如String,Integer的才会用resultType,而自定义的类,一般都用resultMap做字段与实体类属性的映射

5.3.2根据阿里的开发手册,也是resultMap优先

为了解耦

6.Closing non transactional SqlSession异常,竟然是因为sql多写了个分号;

 我用了mybatis分页拦截PageHelper,再执行过程中后边加了LIMIT,然后因为这个<select>里边已经写了分号,所以整个的sql就变成了.....;LIMIT  ,看到了吧,limit前边有个分号,自然执行出错

如果你不涉及到分页,那这样写可能不会报错

7.mybatis支持多种数据库

以下为理论知识没有亲自实践

databaseId

比如你写了了两个select   id一样  然后就databaseId不一样  一个是mybasql  一个是oracle 那么当url配置的是达梦时候就会执行dm那个select  当url配置的是h2时候就会执行h2那个select

加上databaseId这个属性  属性值和url中jdbc:后边的值一致

spring.datasource.druid.url=jdbc:h2:tcp://localhost:25000/openapi;AUTO_SERVER=TRUE;
    <select id="isExist" resultType="Boolean" databaseId="mysql">
        SELECT EXISTS(SELECT 1 FROM `${db}`.test_table WHERE table_id=#{tableId} LIMIT 1)
    </select>

    <select id="isExist" resultType="Boolean" databaseId="oracle">
        SELECT COUNT(*) FROM ${db}."test_table " WHERE "table_id"=#{tableId}
    </select>

 为什么我会碰到这个知识点,因为踩了一个坑

公司要从mysql数据库切换到达梦数据库,然后遇到一个字段begin是关键字,切换为达梦后执行是有问题的。

我采取的几种方式

1.我之前的方式是把字段给改了名字begin->beginDM。

2.然后又考虑到有别人用所以又把名字改回去beginDM->begin,并且把关键字加了双引号 "begin"。

3.但是,如果哪天我们再切换成别的数据库怎么办?所以,搜索了mybatis如何兼容多种数据库:databaseId

8.#{}与${}

8.1#{}是预编译的,就是给传进来的值加了单引号

所谓预编译理解下来即:
数据库驱动在发送sql和参数到DBMS(数据库管理软件,暂时这么理解吧)之前,先对sql语句进行编译处理,
之后DBMS则可以直接对sql进行处理,不需要再次编译,提高了性能。
这一点mybatis 默认情况下,将对所有的 sql 进行预编译处理。

mybatis在把sql传入DBMS之前,先动态解析,再预编译

#{ } 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符,变量替换完成在DBMS中。
${ } 仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换,DBMS拿到的即为执行的sql。

${}在动态解析那步,就进行字符串替换,所以我们在控制台能看到即将执行的sql

#{}在预编译那步,才替换为?  所以我们在控制台看到的是带?的sql

8.1.1正例

如想执行的sql是select id from user where user_name = #{userName}

然后入参userName为 张三 那预编译就变成'张三' 即 select id from user where user_name = '张三'

8.1.2反例

如想执行的sql是select id from #{table} where user_name = '张三'

然后传入user 那么sql就变成了 select id from 'user' where user_name = '张三'

此时sql无法执行

8.2 ${}不是预编译

所以上一个反例  应该写成select id from ${table} where user_name = #{username}

8.3 但是阿里建议最好用#{}而不是${}

因为容易出现sql注入,${}是一个简单的String替换,字符串是什么,解析就是什么

${}不安全,容易引起sql注入,举个栗子

select * from ${tableName} where name = #{name}

如果我的tableName传入的是

user;delete user;--

那么完整的sql就变成了

select * from user;delete user;-- where name = ?

其中--where name =?就变成了注释 不会执行  而且还把我user表删了  所以确实不安全

8.4总结  ${}适用于表名  #{}适用于传字段值

9.where 1=1的优雅代替

<where>标签

9.1改造前

SELECT  *
        from pf1001
        where 1=1
        <if test="attrName != null" >
            and instr(attrName,#{attrName, jdbcType=VARCHAR})>0
        </if>
        <if test="attrCode != null" >
            and instr(attrCode,#{attrCode, jdbcType=VARCHAR})>0
        </if>
        <if test="scopeType != null" >
            and scopeType=#{scopeType}
        </if>
        <if test="scopeId != null" >
            and scopeId=#{scopeId}
        </if>

9.2改造后

SELECT  *
        from pf1001
        <where>
            <if test="attrName != null" >
                and instr(attrName,#{attrName, jdbcType=VARCHAR})>0
            </if>
            <if test="attrCode != null" >
                and instr(attrCode,#{attrCode, jdbcType=VARCHAR})>0
            </if>
            <if test="scopeType != null" >
                and scopeType=#{scopeType}
            </if>
            <if test="scopeId != null" >
                and scopeId=#{scopeId}
            </if>
        <where>

9.3<where>标签会把排名第一的and自动去掉

10.<set>标签

<where>是把第一个and去掉,set是把最后一个逗号,去掉

如下sql,就算classname为空,执行也不会报错

 <update>
    update user 
    <set>
      <if test="name != null and classname.length()>0">
                name = #{name},
           </if>
      <if test="classname != null and classname.length()>0">
                classname= #{classname}
           </if>
    </set>
    where id = #{id}
  </update>

11.<trim>标签

以上,<where>和<set>可以看作<trim>的简写

<where>相当于<trim prefix="where"  prefixOverrides="and | or">

<set>相当于<trim prefix="set"  suffixOverrides=",">

12.<foreach>标签

批量插入


    <insert id="insert">
        insert into user(
        user_name,
        user_info,
        create_time)
        values
        <foreach collection="list" item="user" separator=",">
            (
            #{user.userName},#{user.userPassword},#{user.userEmail},
            #{user.userInfo},#{user.headImg,jdbcType=BLOB},
            #{user.createTime,jdbcType=TIMESTAMP}
            )
        </foreach>
    </insert>

separator表示分割方式  separator="," 就是用逗号,隔开

比如写一个批量插入的sql

INSERT INTO `devdb`.`testorder`(`order1`, `order2`) VALUES (1, 2),(3,4),(3,4),(3,4),(3,4),(3,4),(3,4);


13.join 字段加索引可以提升效率

14.hibernate与mybatis

hibernate无法自定义组装sql,对于复杂关联表以及复杂sql支持弱

但是开发速度快,省事

如果一个项目要求几天开发完,首选hibernate

但是hibernate性能不如mybatis

15.mybatis通过动态代理拿到实现类----xml

目前只知道mybatis这种模式与动态代理有关:通过Mapper 动态代理机制,可以只编写接口以及方法的定义

xml的namespace是mapper的全路径名

<mapper namespace="com.wish.biz.pf.productfactory.dao.mapper.Pf1001Mapper">

16.@mapper与mybatis:mapper-locations

@Mapper或者@MapperScan扫描mapper,

mybatis:mapper-locations扫描xml文件

怎么注册成bean?我的理解:通过mybatis-spring中间件,把扫描到的注册成bean

当我们在使用MyBatis时,一般是编写一个Mapper接口和一个Mapper.xml文件,
我们都知道接口是不能直接被实例化的,然而我们一般在service层中编写的注入属性都是Mapper接口,
那么Spring是如何对该接口进行实例化的呢?

一般而言,如果我们使用Spring和MyBatis作为我们的开发框架时,在搭建开发环境的时候,
都会做一个Spring与MyBatis的整合,使用到的就是MyBatis-Spring这个中间件,
MyBatis-Spring中间件帮我们把mapper接口和mapper.xml文件对应的代理类注册到Spring中,
因此,我们在service层中就能根据类型注入,将对应mapper接口的代理类注入到service层中,
我们才能够调用到对应的方法

17.@MapperScan

作用:

1.直接扫描路径,不用每个接口上都写@Mapper : 在接口类上添加了@Mapper,在编译之后会生成相应的接口实现类

2.指定要变成实现类的接口所在的包,然后包下面的所有接口在编译之后都会生成相应的实现类

到现在,我也一直觉得,mybatis的xml就相当与mapper接口的实现类,不然我们怎么都用@Autowired注入mapper接口

18.mybatis通过默认得无参的构造方法实例化对象

如果对象中有有参得构造方法,需要再mybatis中配置<constructor>,否则mybatis无法实例化对象

19.什么时候用到构造方法

查出来数据需要转化成对象时,要用到那个对象得构造方法

20.拿到数据库生成得主键

数据库主键设置为自动递增时,程序在insert前不知道新的主键是什么

<insert id="insertUser" useGeneratedKeys="true" keyProperty="userId"></insert>

原理:数据库产生过的最大主键加1

比如最大主键101,把他删了,再插入一条,主键就是102

同mysql函数

SELECT LAST_INSERT_ID()

复杂写法

<insert id="insert1" parameterType="test1">
        <selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id">
            SELECT LAST_INSERT_ID() as id
        </selectKey>
        insert into test1(
            s1,
            `s2`
        ) values (
            #{s1},
            #{s2}
        )
    </insert>

selectkey中 order为after 是先插入数据后拿到主键

21.<sql id="sqlId">与<include refid="sqlId">重用sql

<sql id="Base_Column_List" >
   attrId, attrCode, createUserId, createTime, operatorId, freeUseChar1, updateTime, attrName, attrDesc, attrType, attrValueType, minLength, maxLength, decimalDigit, paramId, ruleId, scopeType, scopeId, attrMaxValue, attrMinValue, attrKey
  </sql>
 <sql id="table"> pf1001 </sql>
  <select id="selectByExample" resultMap="BaseResultMap" parameterType="pf1001Example" >
    select
    <include refid="Base_Column_List" />
    from <include refid="table"/>
    <if test="_parameter != null" >
      <include refid="Example_Where_Clause" />
    </if>
    <if test="orderByClause != null" >
      order by ${orderByClause}
    </if>
  </select>

22.mybatis入参有map改为实体

把涉及到的参数都整理到实体里,然后改一下parameterType就行  

23.mapper之间的继承

23.1.mapper接口需要继承

public interface Pf1001Mapper extends Pf1001BaseMapper

23.2.namespace要一致,xml的namespace要使用子接口的全路径名

Pf1001BaseMapper.xml

<mapper namespace="com.wish.biz.pf.productfactory.dao.mapper.Pf1001Mapper">

Pf1001Mapper.xml

<mapper namespace="com.wish.biz.pf.productfactory.dao.mapper.Pf1001Mapper">

23.3 继承后  就像java继承一样  可以用父xml的resultMap

实际应用踩坑

mybatis的自动生成插件生成了mapper A和xml A之后  我新增了extendMapper B 和extendXml B  在mapper B中继承了mapper A

我在B中想使用A的resultMap  但是执行时报B中没有这个resultMap  原因是我xml A中的namespace没有改成mapper B

注意这里

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值