Mybaits官网:MyBatis中文网
文档地址:D:\MybatisTest
MybatisTest为父工程,MybatisTest1为子工程
大概的项目结构就这样,随便创建maven工程
引入的依赖
<dependencies>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
......先创建个数据库名 mybatis
utf8和utfmb4区别:
utfmb4 兼容 utf8 且可以显示更多的字符,一般都用不到,选择utfmb4,只会浪费空间,通常,用的utf8,我喜欢用utf8mb4(个人习惯);
排序
常用的就是 utf8_general_ci
utf8_general_ci:不缺分大小写,核对速度快,准确度稍差
utf8_general_cs:区分大小写
utf8_bin:字符串每个字符串用二进制数据编译存储。 区分大小写,而且可以存二进制的内容
utf8_unicode_ci:准确度高,但校对速度稍慢
然后表
CREATE TABLE `user` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT comment '主键',
`user_name` varchar(50) NOT NULL comment '用户名',
`age` smallint(6) NOT NULL comment '年龄',
`addr` varchar(100) default NULL comment '地址',
`gender` varchar(10) default NULL comment '性别',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '用户表';
insert into user(user_name,age,addr,gender) values ('哈哈',18,null,null);
insert into user(user_name,age,addr,gender) values ('张三',33,'北京朝阳区',null);
insert into user(user_name,age,addr,gender) values ('李四',66,'陕西西安',null);
有表就肯定有对应的实体类
运用Mybatis的第一种方式:
所有的都是官网文档抄的,但是学会抄,学会看文档没有3-5年经验还是比较麻烦的,当然我属于笨的.
大概的逻辑,其实mybatis-config.xml这个名字随你自己
以下是各个文件的所有配置信息,且注释都写的较为详细
mybatis-config.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>
<!--
定义一些键值对,可以在当前文件中通过${}来使用,也可以在当前项目任意的mapper.xml文件中使用(就是下面指定的mapper文件)
resource:指定外部文件
-->
<properties resource="db.properties">
<property name="aaaa" value="bbbb"/>
</properties>
<!--
environments:数据库环境配置
default:默认使用哪个库
id:指定使用的数据库id
-->
<environments default="development">
<environment id="development">
<!--
transactionManager:事务管理器
type取值有两种:
1:type="JDBC",使用java.sql.Connection管理事务
2:type="MANAGED",文档解释,几乎什么都不做,它不提交或回滚一个连接,而是让容器来管理事务的整个生命周期
-->
<transactionManager type="JDBC"/>
<!--
dataSource:数据源
type="POOLED",使用连接池
type="UNPOOLED",不使用连接池
type="JNDI",JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性
-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--告诉mybatis去哪里找mapper文件-->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
UserMapper.xml
<?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="abc"><!--现在可以随意取名,以后命名空间要指定接口全名-->
<select id="selectUserById" resultType="com.hrui.pojo.User">
select * from user where user_id = #{userId}
</select>
</mapper>
db.properties
测试的结果
与数据库并不一致,原因,实体类字段是驼峰命名
下面介绍下目前情况下,解决此问题的几种方式:
1.最简单的,通过sql
<?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="abc"><!--现在可以随意取名,以后命名空间要指定接口全名-->
<select id="selectUserById" resultType="com.hrui.pojo.User">
select
user_id as userId,
user_name as userName,
age,
gender
from user where user_id=#{userId}
</select>
</mapper>
这样就解决了.
2.通过resultMap
<?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="abc"><!--现在可以随意取名,以后命名空间要指定接口全名-->
<resultMap id="userMap" type="com.hrui.pojo.User">
<!--只需要配置命名不同的,相同的配置了也可以-->
<result property="userId" column="user_id"></result>
<result property="userName" column="user_name"></result>
</resultMap>
<select id="selectUserById" resultMap="userMap">/*这里需要改成resultMap*/
select * from user where user_id=#{userId}
</select>
</mapper>
也可以解决
3.在mybatis-config里配置,注意下此时UserMapper.xml我已经还原成刚开始的样子
<?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="abc"><!--现在可以随意取名,以后命名空间要指定接口全名-->
<select id="selectUserById" resultType="com.hrui.pojo.User">
select * from user where user_id = #{userId}
</select>
</mapper>
在mybatis-config.xml里配置下面这个
在mybatis-config.xml配置的时候注意一个顺序,就是configuration里按下面排序来写,别问为什么,规定.......
看下输出,日志也可以看到了
还可以在mybatis-config.xml中指定实体包别名,这样以后resultType="user"就不用写全名,只要类名小写就可以了
这里提前讲一点,关于MyBatis的事务与缓存
MyBatis框架是对JDBC的封装.
看下图
MyBatis框架事务控制方式有两种
1.type="JDBC":让java.sql.Connection对象管理事务(下面说这种)
2.type="MANAGED":让外在容器管理事务
在type="JDBC"情况下:
Connection 对象的 setAutoCommit()方法来设置事务提交方式的。自动提交和手工提交。
Connection通过commit()方法提交事务,国通rollback()方法回滚事务.但是默认情况下,Mybatis将自动提交功能关闭了,改为了手动提交.即程序中需要显示的对事务进行提交或回滚.看下原先输出的日志
在获得SqlSession的时候,如果openSession(),是无参或者是false,则必须手工提交事务,
如果openSession(true),则为自动提交事务,在执行完增删改后无需commit(),事务自动提交,我们可以发现,在使用SpringMVC或者是SpringBoot整合Mybatis时,我们执行增删改后无需commit(),
那么可以肯定设置了openSession(true)
---------------------------------------------------------------------------------------
缓存:将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。mybatis提供查询缓存,用于减轻数据库压力,提高数据库性能。
缓存执行机制:在进行数据库访问时,首先去访问缓存,如果缓存中有要访问的数据,则直接返回客户端,如果没有则去访问数据库,在库中得到数据后,先在缓存放一份,再返回客户端。如下图。
mybaits提供一级缓存和二级缓存。默认开启一级缓存。
关于一级缓存和二级缓存暂时可以这么理解:sqlSession层面的是一级缓存
Mapper(namespace)层面属于二级缓存
看下具体代码执行
如图只进行了一次查询
那么我代码改下:如图 如果close了 那么其实就是另外一个sqlSession,缓存自然清空了
执行结果
再看下,同一个sqlSession如果commit了,那么也会清空缓存
那么结论就是:同一个sqlSession会保留一级缓存,因为SpringMVC,SpringBoot整合Mybatis时默认增删改是自动commit的,因此数据更新后,同一个sqlSession内的缓存会消失,再次查找时自动缓存.
Mybatis默认在sqlSession层面开启了一级缓存,如何开启二级缓存:
mybaits的二级缓存是mapper范围级别,除了在mybatis-config.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存,并且要让实体类实现serializable接口。
具体步骤:
1.在mybatis配置文件中加入下面语句
2.在UserMapper.xml文件中开启二级缓存,使用<cache></cache>
3.实体类实现序列化接口java.io.serializable
二级缓存结论是:只要有一个sqlsession.commit,后多个sqlSession会公用一个Mapper
这里本想先说下resultMap,一会吧
因为现在还没有用到springBoot,以后在SpringBoot中配置相关如下
在SpringBoot中yml文件配置mybatis和mybatis-plus
总结mybatis的第一种写法:
1.创建mybatis的配置文件mybatis-config.xml,名字随便取
2.创建SqlSessionFactory
3.通过SqlSessionFactory创建SqlSession
4.创建Mapper文件
5.使用SqlSession对象执行sql
下面介绍mybatis的最终写法(mapper接口写法)
1.创建接口,写对应抽象方法,如图
2.改写UserMapper.xml,改写命名空间namespace为接口全名,方法名为id
3.执行
很多人在学习Mybatis时,可能遇到过很多版本,老师有时会讲将接口和.xml放同一包中,且名字要相同,导致学习很混乱,怎么一会必须同包中且名字要相同,一会又可以不用放在一起,名字也可以不同
这里带你完全解开这个结(因为自己是强迫症患者,曾经在不太明白是这个问题困扰很久,现在也还是小白)
1.在注册映射文件时使用<mappers><package name="包名"></mappers>标签时,需要映射文件名和接口名一样,不然会报错。
2.在注册映射文件时使用<mappers><mapper class="">mapper</mappers>标签的class属性时,需要映射文件名和接口名一样,不然会报错。
3.在注册映射文件时使用<mappers><mapper resource="org/xx/demo/mapper/xx.xml"/></mappers>,不需要映射文件名和接口名一样
另外相信很多同学在学习Mybatis时很肯定多个参数时候必须加@Param注解
那么也同样的告诉你在mybatis 3.4.1之后StringBoot整合,允许不加 @Param指定参数名称,自动会以入参的名称作为param key,嘿嘿.
第二种方式总结(最终写法)
1.创建mybatis的配置文件mybatis-config.xml,名字随意
2.创建sqlSessionFactory
3.通过sqlSessionFactory创建sqlSession
4.创建mapper接口,在.xml里指定namespace为接口全名 sql id为接口方法名
5.使用sqlSession对象获取接口代理
6.使用代理对象执行
简单增删改:
新增:
执行
这里注意 try()实现了Closeable可以自动关闭,但是并没有提交,因此这样执行是没有效果的
如果我没有设置自动提交,而主键自增策略已开启,这个自己领悟
这里另外讲下:关于mybatis-config.xml的多环境配置
官方文档:
修改mybatis-config.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>
<!--
定义一些键值对,可以在当前文件中通过${}来使用,也可以在当前项目任意的mapper.xml文件中使用(就是下面指定的mapper文件)
resource:指定外部文件
-->
<properties resource="db.properties">
<property name="aaaa" value="bbbb"/>
</properties>
<settings>
<!--开启日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- 开启驼峰,开启后,只要数据库字段和对象属性名字母相同,无论中间加多少下划线都可以识别 -->
<setting name="mapUnderscoreToCamelCase" value="true" />
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
<!--指定实体类包别名-->
<typeAliases>
<package name="com.hrui.pojo"/>
</typeAliases>
<!--
environments:数据库环境配置
default:默认使用哪个库
id:指定使用的数据库id
-->
<environments default="development">
<environment id="development">
<!--
transactionManager:事务管理器
type取值有两种:
1:type="JDBC",使用java.sql.Connection管理事务
2:type="MANAGED",文档解释,几乎什么都不做,它不提交或回滚一个连接,而是让容器来管理事务的整个生命周期
-->
<transactionManager type="JDBC"/>
<!--
dataSource:数据源
type="POOLED",使用连接池
type="UNPOOLED",不使用连接池
type="JNDI",JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性
-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
<!--另外配置一个数据源development2-->
<environment id="development2">
<!--
transactionManager:事务管理器
type取值有两种:
1:type="JDBC",使用java.sql.Connection管理事务
2:type="MANAGED",文档解释,几乎什么都不做,它不提交或回滚一个连接,而是让容器来管理事务的整个生命周期
-->
<transactionManager type="JDBC"/>
<!--
dataSource:数据源
type="POOLED",使用连接池
type="UNPOOLED",不使用连接池
type="JNDI",JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性
-->
<dataSource type="POOLED">
<property name="driver" value="${driver1}"/>
<property name="url" value="${url1}"/>
<property name="username" value="${username1}"/>
<property name="password" value="${password1}"/>
</dataSource>
</environment>
</environments>
<!--告诉mybatis去哪里找mapper文件-->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
在db.properties里,多加个配置
如上简单的多数据源配置
Mybatis通过SqlSessionFactory获得SqlSession然后去执行sql
关于SqlSessionFactory可以查看简述,觉得写的很好SqlSessionFactory创建过程 - 简书
重归正题:
简单删除
简单更新:
两种方式返回自动生成的主键:实际应用的业务场景很多,比如往一张表插入数据后,需要再另外一张表里对刚插入的数据的Id做记录
第一种方式:useGeneratedKeys+keyProperty
以原先插入数据为例子
这里我再做个例子,一般情况,建表时候会通过mysql自增来生成主键,打个比方,我现在希望的主键
为NBXX00000001这种方式存在,且不讨论这样降低查询效率的事.如何实现呢
建张test表测试下
CREATE TABLE `test` (
`fhbs` varchar(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`age` smallint NOT NULL COMMENT '年龄',
`addr` varchar(100) DEFAULT NULL COMMENT '地址',
`gender` varchar(10) DEFAULT NULL COMMENT '性别',
PRIMARY KEY (`fhbs`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb3 COMMENT='测试表';
现在主键为字符串类型,因此将自增去掉
建对应的实体类
在mybatis-config.xml里将.xml配置上去
创建接口
下面这个工具类用以返回NBXX0000000001格式的
public class PrimaryKey {
// 数字串最小值
private static final char MIN_DATA = '0';
// 数字串最大值
private static final char MAX_DATA = '9';
// 数字串默认从1开始
private static final char START_DATA = '1';
// 默认长度
private static final int DEFAULT_SIZE = 8;
// KeySize的最大数
// Long的最大长度是19位,防止溢出
private static final int MAX_KEYSIZE_VALUE = 18;
// 默认字符串Head
private static final String DEFAULT_HEAD = "KEY";
// 主键字符串头部
private String keyHead;
// 字符串数字位数,不足补0
private Integer keySize = 8;
// 是否允许数字最大之后自增,默认false
private boolean keyInc = false;
// 程序执行开始系统时间
private Long startExecute = 0L;
// 程序执行结束系统时间
private Long finishExecute = 0L;
/**
* 初始化主键字符串格式,默认达到KeySize后不可自增
* @param keyHead 字符串开头部分
* @param keySize 字符串数组长度
*/
public PrimaryKey(String keyHead, Integer keySize) {
super();
/**
* 设置不可自增
*/
if(this.checkSize(keySize))
this.keySize = keySize;
else
this.keySize = this.DEFAULT_SIZE;
if(this.checkHead(keyHead))
this.keyHead = keyHead;
else
this.keyHead = this.DEFAULT_HEAD;
}
/**
* 初始化主键字符串的格式
* @param keyHead 字符串开头部分
* @param keySize 字符串数组长度
* @param keyInc 数值最大值之后是否允许自增
*/
public PrimaryKey(String keyHead, Integer keySize, boolean keyInc) {
super();
if(this.checkSize(keySize))
this.keySize = keySize;
else
this.keySize = this.DEFAULT_SIZE;
if(this.checkHead(keyHead))
this.keyHead = keyHead;
else
this.keyHead = this.DEFAULT_HEAD;
this.keyInc = keyInc;
}
/**
* 返回下一个字符串
* @param currentKey 当前主键
* @return 正常:下一个主键值 = 当前主键 + 1;
* 当字符串数字达到KeySize的最大数时
* KeyInc为true时, 下一个主键字符串返回最大数 + 1
* KeyInc为false时, 返回空值
*/
public synchronized String nextKey(String currentKey) {
// 记录开始执行程序系统时间
this.startExecute = System.currentTimeMillis();
try {
/**
* 去掉首尾空字符
*/
currentKey = currentKey.trim();
if(!this.check(currentKey)) {
System.out.println(PrimaryKey.class.getSimpleName() +
" Error: Input CurrentKey Str Type Illegal, Check '" + currentKey +"' is Right!");
return null;
}
StringBuilder sb = new StringBuilder();
sb.append(this.keyHead);
int charIndex = 0;
for(int i = 0; i < currentKey.length(); i++) {
char symbol = currentKey.charAt(i);
if(symbol >= this.MIN_DATA && symbol <= this.MAX_DATA) {
charIndex = i;
break;
}
}
String dataStr = currentKey.substring(charIndex, currentKey.length());
Long dataNum = Long.valueOf(dataStr);
dataNum++;
if(dataNum < this.splitDataPosition()) {
for(int i = 0; i <= this.keySize - String.valueOf(dataNum).length() - 1; i++) {
sb.append(this.MIN_DATA);
}
sb.append(dataNum);
}else if(dataNum >= this.splitDataPosition() &&
dataNum < this.maxDateNumber()) {
sb.append(dataNum);
}else{
// 超过大小最大数时
if(this.keyInc) {
sb.append(dataNum);
}else{
// 允许自增标志位false的时候返回空值
return null;
}
}
return sb.toString();
} catch (Exception e) {
System.out.println(e.toString());
return null;
} finally {
this.finishExecute = System.currentTimeMillis();
// System.out.println(PrimaryKey.class.getSimpleName() + " nextKey() Execute: "
// + (this.finishExecute - this.startExecute) +"ms.");
}
}
/**
* 获取初始化字符串
* @return
*/
public synchronized String initStartKey() {
StringBuilder sb = new StringBuilder();
sb.append(this.keyHead);
for(int i = 0; i < this.keySize - 1; i++) {
sb.append(this.MIN_DATA);
}
sb.append(this.START_DATA);
return sb.toString();
}
/**
* 获取需要补零的最大数字
* @return
*/
private Long splitDataPosition() {
StringBuilder sb = new StringBuilder();
sb.append(this.START_DATA);
for(int i = 0; i < this.keySize - 1; i++) {
sb.append(this.MIN_DATA);
}
return Long.valueOf(sb.toString());
}
/**
* 获取最大数
* @return
*/
private Long maxDateNumber() {
StringBuilder sb = new StringBuilder();
for(int i = 0; i < this.keySize; i++) {
sb.append(this.MAX_DATA);
}
return Long.valueOf(sb.toString());
}
/**
* 简单的验证空值
* @param key
* @return
* @throws Exception
*/
private boolean check(String key) throws Exception {
try {
// 空值验证
if(key == null || key.equals(""))
return false;
// key字符串长度验证
if(key.length() <= this.keyHead.length())
return false;
// 是否符合初始化串开头验证
String head = key.substring(0, this.keyHead.length());
if(!head.equals(this.keyHead))
return false;
/**
* 串数字长度验证,当允许最大熟自增时候不检测
* 当不允许达到最大数字时验证长度合法性
*/
String data = key.substring(this.keyHead.length(), key.length());
if(data.length() != this.keySize && !this.keyInc)
return false;
// 验证是否是数字串,通过一个转换变量
for(int i = 0; i < data.length(); i++) {
char symbol = data.charAt(i);
if(symbol > this.MAX_DATA || symbol < this.MIN_DATA) {
return false;
}
}
return true;
} catch (Exception e) {
throw e;
}
}
/**
* 验证输入的KeySize合法性
* @param keySize
* @return
*/
private synchronized boolean checkSize(Integer keySize) {
if(keySize != null && keySize > 0
&& keySize <= this.MAX_KEYSIZE_VALUE)
return true;
return false;
}
/**
* 验证输入的KeyHead,条件全部要求是字母
* @param keyHead
* @return
*/
private synchronized boolean checkHead(String keyHead) {
if(keyHead != null && !keyHead.equals("")) {
for(int i = 0; i < keyHead.length(); i++) {
char symbol = keyHead.charAt(i);
if(symbol >= this.MIN_DATA && symbol <= this.MAX_DATA) {
return false;
}
}
return true;
}
return false;
}
}
因为没有自增,因此第一次插入时候需要直接给值,以后插入就可以根据返回的主键来
感觉效果并不好,当然一般进行插入时候前端返回回来比如说NBXX000000005,放进去可以生成一个NBXX000000006出来,但感觉太low
这里一会补上....
第二种方式:selectKey子元素
测试
关于下划线列明自动转驼峰和sql日志打印,一开始我们就配好了,还有很多其他配置
可以查看官方文档
关于日志打印,
下图写错了.........
如果是如下配置,需要引入具体jar包,比如下面需要引入SLF4J,因为我本身引入了lombok就自然有了SLF4J的jar包,下图,原先图配错了,后期补了下
注意下,再下面演示
returnInstanceForEmptyRow | 当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2) |
时候,当版本改成3.4.2以上后 log日志如果单纯依赖lombok就出错了 所以还是引入下依赖
关于日志文件
关于列值为空的处理
当该列为null时,说明mybatis是不会处理这些列的
测试
如何将为null的数据也返回回来呢?配置下
关于ReturnInstanceForEmptyRow,如图举例
这种情况,如果我们 select addr form user where user_id=1去查询
解决办法
结果...........................
问题出在了哪呢?
关于入参的讲究
当入参只有只有一个参数且是基本类型(含包装类)或者字符串
mapper接口中的参数名字随意取
比如
返回为null其实就是可以
再测一个
当参数为conllection/list/set/数组的时候
测试参数为Collection
比如我们要查询 user_id为 1,2,3的用户集合
select * from user where user_id in(1,2,3);
现在下面index去掉没事
再换种写法,一样可以
再换种写法,将collection="collection"
也是可以的
所以当参数为Collection 或者List的时候 collection="list" conllection="conllection"都可以
当是Set时候要用collction 是数组时候 要用array不演示了
当参数是javabean时候,就是比如User user的时候,写该实体类的属性名即可(前面写过很多,不演示)
当参数为Map的时候
测试参数为多个的时候
比如现在我想查询user表中年龄小于30且名字含有"小"字的用户
user改成user_name
那将参数换成arg0,arg1试试
测试成功 说明用arg0,arg1是可以的 如果还有就用arg2 arg3 arg4 .....
用param1 param2试试
测试结果:用param1 param2也是可以的 如果还有 param3 .....
那么一定要用arg0 arg1或者param1 param2吗 如何用有意义的参数名或者说如何自定义参数名
第一种方法:在userActualParamName的值是true的情况下加入编译参数也就是说这里userActualParamName默认情况下true,加入的是编译参数,如何改成自定义参数名,改成false行不行呢??
在官方文档
在mybatis-config.xml里配置下
意思就是说
useActualParamName=true的时候可以用 arg0 arg 1或者param1 param2
useActualParamName=false的时候可以用 0 1或者param1 param2
下面做个小demo
通过反射来获取a1方法里的参数名
引入spring框架
惊不惊喜,意不意外(这就是MyBatis3.4.1之后在Spring整合MyBatis和Spring整合MyBatis不需要@Param的原因吗,没人和我说我猜的)
这里是因为我引入了Spring框架
不适用Spring框架怎么办呢?
在编译时候加上
<configuration>
<parameters>true</parameters>
</configuration>
效果和使用Spring的工具类LocalVariableTableParameterNameDiscoverer的效果一样都是获取真正的方法参数名
具体使用在子工程的下面加上,就是说在编译时候把参数名变成自定义的
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<parameters>true</parameters>
</configuration>
</plugin>
</plugins>
</build>
重新编译下,另外重新将这个改回来,默认就是true,还是让它使用编译后的参数
只不过现在我们编译后的参数名是自定义的了
哇哦哇哦
SpringBoot就是这么干的
第二种方法:通过@Param注解指定(SpringBoot整合Mybatis3.4.1版本后不用加@Param注解)
这个就不演示了....
关于MyBatis封装入参的原理:org.apache.ibatis.reflection.ParamNameResolver#getNamedParams
MyBatis ps.setXxx取值原理:
org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
关于返回值的问题insert就是插入几条返回几,delete也一样
update的话需要注意下
新写个demo
这里有个问题,就是无论更新了多少次,这个返回值一直是1
我们无法看出来,到底数据更新了没有,还是说和原先比一个值都没变,这个问题如何解决,这里用Mybatis就没有用了,要依靠JDBC来解决
具体做法
在db.properties的url后面加上&useAffectedRows=true,这样就返回真正影响的行数
我们再次更新一条和数据库一样的数据时候,返回值为0
关于select的返回值
常用的不写了,写点不常用的
比如说返回List<user>时候,
这个resultType是里面javabean
比如返回List<Map<String,Object>>
关注下这种数据格式
[{gender=null, user_id=1, user_name=小红, addr=null, age=18}, {gender=null, user_id=2, user_name=张三, addr=北京朝阳区, age=33}, {gender=null, user_id=3, user_name=李四, addr=陕西西安, age=66}]
关于resultMap这里要讲下,它其实上面已经说过了,可以帮助我们减少后端代码,
比如说关联表的查询,1对1,1对多等等
在1对1关系中使用association
1对多关系中使用conllection
举例说明
先插入三张表
drop table if exists author;
CREATE TABLE `author` (
`id` bigint(20) NOT NULL AUTO_INCREMENT comment '主键',
`name` varchar(50) NOT NULL comment '作者名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '作者表';
insert into author(id,name) values (1,'哈哈');
insert into author(id,name) values (2,'呵呵');
drop table if exists article;
CREATE TABLE `article` (
`article_id` bigint(20) NOT NULL AUTO_INCREMENT comment '主键',
`title` varchar(50) NOT NULL comment '标题',
`author_id` bigint(20) not NULL comment '外键,作者表的id',
PRIMARY KEY (`article_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '文章表';
insert into article(article_id,title,author_id) values (1,'mybatis使用教程',1);
insert into article(article_id,title,author_id) values (2,'mybatis源码剖析',2);
drop table if exists remark;
CREATE TABLE `remark` (
`id` bigint(20) NOT NULL AUTO_INCREMENT comment '主键',
`cont` varchar(500) NOT NULL comment '评论内容',
`article_id` bigint(20) not NULL comment '所属文章id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '评论表';
insert into remark(id,cont,article_id) values (1,'这个教程是良心教程,学到了很多',1);
insert into remark(id,cont,article_id) values (2,'666+999',1);
insert into remark(id,cont,article_id) values (3,'分析的不错',2);
作者表,文章表,评论表.
站在文章表的立场上:文章表里每条数据都对应作者表的每条数据(作者).1对1关系
站在文章表的立场上:文章表里的每条数据都对应着评论表里的多条数据.1对多关系
刚好可以做演示
建完表,第一件事一般就是建实体类
就不建VO了,直接在实体类上加了
比如先查出article表的所有信息 用resutlMap的使用
通过resultMap里的association用于1对1关系
resultMap使用collection处理1对多
foreach实现批量插入
一般批量插入
insert into user(user_name,age) values('a',1),('b',2),('c',3)
以上只是插入几十条,几百条数据,那么如果我需要插入10W条数据呢
居然没报错,10W条数据花了20秒
那么100W条数据呢,几分钟还没完,下去买吃的
100W条耗时622秒
本来是想演示报错的 如下图
结果这样插入居然全成功了,可能是我用的阿里云RDS数据库的原因?????????
咱就当它报错了行吧
这种批量插入报错,因为mysql对要执行的sql语句长度有限制,所有有可能会失败
晕,一般mysql对sql语句的默认长度好像是,查了下本机 4M
我这查了阿里云的是配置了1G.....
查询语句:show VARIABLES WHERE Variable_name LIKE 'max_allowed_packet'
或者show VARIABLES like '%max_allowed_packet%'
此种方式的插入对于少量数据影响不大
但是大量数据时候,一方面是mysql的sql语句长度限制,当然长度限制应该是可以修改的,最重要的一点,太耗时,刚才我100W的数据量插入耗时10分钟
下面介绍一种MyBatis用于批量插入的另一种方式 MyBatis批量插入执行器,BatchExecutor批量插入
表面上看是由SqlSession去执行sql,其实真正执行sql的是Executor
这样更慢 耗时
不是批量的话,执行流程:预编译->设置参数->执行.如此往复
批量的话:程序把sql发到mysql,让mysql做预编译,然后一次性提交
但是这里不能这么做,一般建议2000到1W条之间提交一次
这样也不行,这里暂停吧.改天专门写篇批量处理的
关于sql和include标签
关于if和where
if的作用
测试
怎么办呢?
测试 ,注意下刚开始报错了,后来将注释删除了就好了,说明/* xxxxxx */注释影响sql执行
<where>标签,比如 userName没有值 age有值
那么sql会变成where and age>=2 这样<where>标签会自动将这个and或者or去掉
演示下
关于choose和when和otherwise
为了演示,RequestPage里新加两个字段
比如现在有这样一个逻辑
如果排序的字段不是null或者"",就按你传过来的这个字段排序,不然就按XXX排序
现在还没有传值
sortField写错了下面
这里注意下
关于set标签:用于动态包含需要更新的列,忽略其他不更新的列
比如原先写过的一个更新例子
比如我现在只想更新名字,其他的不更新看下数据库
结果原先的地址没有了
改下
结果执行
那么这个逗号怎么去掉呢,其实和where的问题差不多
用<set>会去掉末尾的逗号
关于trim标签
trim标签包含了where和set的功能
以上面的set为例
用于避免相同数据的更新返回值为1