文章目录
- MyBatis
- 1、概述
- 1.2、特性
- 1.3、安装
- 1.4、对比
- 2、搭建Mybatis
- 2.1、创建一个Maven项目
- 2.2、然后使用project structure确定java版本
- 2.3、setting>build->build tools>maven,配置maven仓库、镜像、目录
- 2.4、设置打包方式(子模组)
- 2.5、引入驱动依赖
- 2.6、创建测试用表
- 2.7、创建对应实体类
- 2.8、创建mybatis的核心配置文件
- 添加关键配置
- 2.9、创建用户接口
- 2.10、创建Mybatis的映射文件
- 2.11、config中指定mapper
- 2.12、编写测试类
- 3、引入log4j
- 3.1、引入依赖
- 3.2、创建log4j.xml
- 4、Mybatis整合Sqlsession工具类
- 5、简单update
- 6、简单delete
- 7、简单select
- 使用ID查询单个用户信息
- 查询所有用户信息
- 8、Mybatis配置文件核心配置项
- 9、创建类型模板
- 10、MyBatis获取参数值方式
- 10.1、单个字面量类型的参数
- 10.1.1、如何使用
- 10.2、多个字面量类型的参数
- 10.2.1、如何使用
- 10.3、使用map键值来进行参数输入
- 10.4、使用对象进行参数输入
- 10.5、使用注解的方式进行参数输入
- 11、mybatis的各种查询
- 11.1、简单查询
- 11.1.1、查询某个实体类对象
- 11.1.2、查询一个list集合
- 11.1.3、查询一个具体值
- 11.1.4、查询一个Map集合
- 11.1.5、将多条数据放入Map
- 11.2、特殊查询
- 11.2.1、模糊查询
- 12、批量删除
- 13、动态设置表名
- 14、添加功能获取自增的主键
- 14.1、使用JDBC的方式
- 14.2、使用Mybatis的方式
- 15、自定义映射resultMap
- 15.1、处理字段和属性的映射关系
- 第一种方式:设置列别名
- 第二种方式:配置mybatis-config.xml全局变量
- 第三种方式:使用resultMap
- 15.2、处理多对一级联映射关系Association
- 第一种方式:对象.属性
- 第二种方式:关联 association
- 第三种方式:分步查询
- 分步查询的好处
- 15.3、处理一对多级联查询Collection
- 第一种方式:Collection + ofType
- 第二种方式:分步查询的方式
- 16、动态SQL
- 16.1、IF 多条件查询
- 第一种方法-恒成立公式
- 第二种方法-where标签
- 第三种方式-trim
- 16.2、Choose、when、otherwise
- 16.3、foreach
- 批量添加(以集合的形式遍历)
- 批量删除(数组方式)
- 16.4、SQL片段
- 17、Mybatis缓存
- 17.1、一级缓存
- 17.2、二级缓存
- 相关配置
- 17.3、查询的顺序
- 18、Mybatis逆向工程
- 19、mybatis配置分页插件PageHelper
MyBatis
1、概述
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。
1.2、特性
1) MyBatis是 支持定制化SQL、存储过程以及高级映射的优秀的持久层框架
2) MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集
3) MyBatis可以使用简单的XML或注解用于配置和原始映射, 将接口和Java的POJO (Plain Old Java
Objects,普通的Java对象)映射成数据库中的记录
4) MyBatis 是-个半自动的ORM (Object Relation Mapping)
1.3、安装
要使用 MyBatis-Spring 模块,只需要在类路径下包含 mybatis-spring-2.0.7.jar
文件和相关依赖即可。
如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.7</version>
</dependency>
1.4、对比
JDBCSQL夹杂在JAVA代码中,耦合度高维护不易,且容易频繁修改代码冗长,效率低
Hibernate和JPA操作简单、开发效率高长难SQL需要绕过框架内部自动生成SQL,不易做优化基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难反射操作太多,导致数据库性能下降
Mybatis轻量级,性能出色SQL和JAVA编码分开,功能边界清晰开发效率稍逊于Hibernate,但是完全能够接受
2、搭建Mybatis
MySQL驱动版本
MySQL5使用jdbc5驱动,驱动类使用:com.mysql.jdbc.Driver
MySQL8使用jdbc8驱动,驱动类使用:com.mysql.cj.jdbc,DriverMySQL连接URL
MySQL5使用的url:
jdbc:mysql://localhost:3306/ssm
MySQL8使用的url:
jdbc:mysql://localhost:3306/ssm?serverTimezone=GMT
2.1、创建一个Maven项目
可以先创建一个空项目,然后在空项目里面创建子项目(子模组)
2.2、然后使用project structure确定java版本
2.3、setting>build->build tools>maven,配置maven仓库、镜像、目录
2.4、设置打包方式(子模组)
因为当前项目只是为了测试mybatis,所以暂时用不到war包的打包方式,这里指定为jar包
2.5、引入驱动依赖
<dependencies>
<!-- Mybatis核心--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.9</version></dependency>
<!-- junit--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency>
<!-- MySQL--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.18</version></dependency></dependencies>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wYszOfPl-1690788149150)(Mybatis.assets/image-20220807134155129.png)]
2.6、创建测试用表
drop table if exists t_user;
create table t_user(id int(10) primary key auto_increment,user_name varchar(20),password varchar(20),age int(10),gender char(2),email varchar(50)
);#这里建议吧user_name改为username,java实体类名称也为username
#建议Java实体类与数据库中数据字段的名称相同mysql> desc t_user;
+-----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| user_name | varchar(20) | YES | | NULL | |
| password | varchar(20) | YES | | NULL | |
| age | int | YES | | NULL | |
| gender | char(2) | YES | | NULL | |
| email | varchar(50) | YES | | NULL | |
+-----------+-------------+------+-----+---------+-------+
6 rows in set (0.03 sec)
2.7、创建对应实体类
在实体类中加一个Lombok工具的依赖:
<!-- LomBok-->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.10</version>
</dependency>
User类
package com.lobo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;/*** className: entity<br/>* author: MacieSerenity <br/>* date: 2022-08-07 13:49**/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class entity {/*** id 用户ID*/private Integer id;/*** userName 用户名*/private String userName;/*** password 用户密码*/private String password;/*** age 用户年龄*/private Integer age;/*** gender 用户性别*/private String gender;/*** email 用户邮箱*/private String email;
}
2.8、创建mybatis的核心配置文件
可以用任何名称,习惯上使用mybatis-config更有辨识度
核心配置文件的作用是主要用于配置连接数据的环境,以及Mybatis的全局配置信息
核心配置文件存放在src/main/resources目录
映射文件的主要作用是操作数据库
在resource文件夹下创建一个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></configuration>
可以创建到IDEA的模板内:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4lFuWJyS-1690788149155)(Mybatis.assets/image-20220807140452882.png)]
https://blog.csdn.net/zhhongying/article/details/123356380
添加关键配置
<?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="db-source-config.properties" />注意:以上properties文件中的键值对必须要跟下面${ }中的变量相同,若properties文件中命名为jdbc.username=root则下面配置文件中的变量则取为${jdbc.username}<!-- 配置连接数据库的环境 默认:development--><environments default="development"><environment id="development"><transactionManager type="JDBC" />
<!-- 数据源--><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的映射文件--><mappers>
<!-- <mapper resource=""></mapper>--></mappers></configuration>
db-source-config.properties
#db-source-config.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatisstudy?useUnicode=true&characterEncpding=utf8&serverTimezone=GMT
username=root
password=li1473606768
2.9、创建用户接口
Mybatis中的mapper接口相当于以前的Dao,但是区别在于mapper仅仅是接口,且不需要提供具体的实现类。
package com.lobo.mapper;/*** className: UserMapper<br/>* author: MacieSerenity <br/>* date: 2022-08-07 14:18**/
public interface UserMapper {/*** 插入一条用户数据* @return int SQL执行完成后影响的行数*/int insertUser();
}
2.10、创建Mybatis的映射文件
相关概念:ORM(Object Relationship Mapping)对象关系映射
java中的类指代数据库中的一个表,java类中的某个属性代表数据库中的某个字段,java中的某个对象指代数据库中的一条记录。
映射文件命名规则: 具体的类名+Mapper 如 实体类为User
则xml文件名应该为:UserMapper
实体类:User
接口类:UserMapper 或者 IUserMapper
xml映射文件:UserMapper.xml
mapper映射文件:
头标签:
https://blog.csdn.net/black_tomorrow/article/details/122602651
<?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=""><select id="" resultMap=""></select>
</mapper>
在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="com.lobo.mapper.UserMapper"> 一定要指定 对应的接口 的实体类包名<!-- mapper接口和映射文件要保证两个一致:
namnespace与对应的接口的实体类
xml文件中各个方法名与UserMapper接口中的方法--><insert id="insertUser" parameterType="com.lobo.entity.User">insert into t_user(user_name,password,age,gender,email)values('admin','123',18,'男','admin@ad.com');</insert>
</mapper>
2.11、config中指定mapper
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VGPsUW5P-1690788149160)(Mybatis.assets/image-20220807145650865.png)]
2.12、编写测试类
在test/java文件夹下创建test文件
test/java/com/test.java
package com;
import com.lobo.entity.User;
import com.lobo.mapper.UserMapper;
//注意下面写的Resources的包是ibatis里面的,不要弄错了
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
/*** className: test<br/>* author: MacieSerenity <br/>* date: 2022-08-07 14:36**/
public class test {@Testpublic void testInsert() throws IOException {//获取核心配置文件的输入流InputStream inputStrea=Resources.getResourceAsStream("mybatis-config.xml");//获取SQLSessionFactoryBuilderSqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();//获取SqlSessionFactory对象SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(inputStrea);//获取SQL的会话对象SQLsession(不会自动提交事务),是Mybatis提供的操作数据库的对象//SqlSession sqlSession= sqlSessionFactory.openSession();//获取SQL的会话对象SQLsession(会自动提交事务),是Mybatis提供的操作数据库的对象SqlSession sqlSession= sqlSessionFactory.openSession(true);//获取UserMapper的代理实现对象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//调用userMapper中接口的方法,实现添加用户信息的功能int result=userMapper.insertUser();//mysql使用的是唯一标识符来确定某个SQL语句的//int result =sqlSession.insert("com.lobo.mapper.UserMapper.insertUser");System.out.println("result="+result);System.out.println(result==1 ? "添加成功":"添加失败");//mybatis不回自动提交事务,需要自己手动提交
// sqlSession.commit();//关闭会话sqlSession.close();}
}结果:
result=1
添加成功
Process finished with exit code 0数据库中
mysql> select * from t_user;
+----+-----------+----------+------+--------+--------------+
| id | user_name | password | age | gender | email |
+----+-----------+----------+------+--------+--------------+
| 1 | admin | 123 | 18 | 男 | admin@ad.com |
+----+-----------+----------+------+--------+--------------+
1 row in set (0.00 sec)mysql>
3、引入log4j
3.1、引入依赖
<!-- log4j日志-->
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version>
</dependency>
3.2、创建log4j.xml
https://blog.csdn.net/u010544643/article/details/86679651
在resources文件夹下创建log4j.xml(必须命名为log4j.xml)
头文件:
<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd"><log4j:configuration debug="true"><appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"><param name="Encoding" value="UTF-8"/><layout class="org.apache.log4j.PatternLayout"><param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m(%F:%L) \n"/></layout></appender><logger name="java.sql"><level value="debug" /></logger><logger name="org.apache.ibatis"><level value="info" /></logger><root><level value="debug" /><appender-ref ref="STDOUT"/></root>
</log4j:configuration>
日志的级别:FATAL 致命>ERROR 错误 >WARN 信息> DEBUG 调试
从左到右越来越详细
4、Mybatis整合Sqlsession工具类
封装获取SQLSession对象
创建utils包,创建SqlSessionUtil类,getSqlSession方法
package com.lobo.utils;import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;
import java.io.InputStream;/*** className: SqlsessionUtil<br/>* author: MacieSerenity <br/>* date: 2022-08-07 17:10**/
public class SqlsessionUtil {public static SqlSession getSqlSession(){SqlSession sqlSession=null;try {//获取核心配置文件InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");//获取SqlSessionFactoryBuilderSqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();//获取SQLSessionFactorySqlSessionFactory sqlSessionFactory= sqlSessionFactoryBuilder.build(inputStream);//获取SqlSession对象sqlSession=sqlSessionFactory.openSession(true);} catch (IOException e) {e.printStackTrace();}return sqlSession;}
}
5、简单update
UserMapper接口类中
/*** 更改一条用户数据* @return int Sql执行完成后影响的行数*/
int updateUser();
userMapper映射文件中
<update id="updateUser">update t_user set user_name = 'root',password = '123456' where id = 1;
</update>
test类中:
@Test
public void testUpdate(){SqlSession sqlSession= SqlsessionUtil.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);int result = mapper.updateUser();System.out.println("result="+result);System.out.println(result==1 ? "添加成功":"添加失败");sqlSession.close();
}
结果:
DEBUG 08-07 17:19:41,819 ==> Preparing: update t_user set user_name = 'root',password = '123456' where id = 1;(BaseJdbcLogger.java:137)
DEBUG 08-07 17:19:41,850 ==> Parameters: (BaseJdbcLogger.java:137)
DEBUG 08-07 17:19:41,867 <== Updates: 1(BaseJdbcLogger.java:137)
result=1
添加成功mysql> select * from t_user;
+----+-----------+----------+------+--------+--------------+
| id | user_name | password | age | gender | email |
+----+-----------+----------+------+--------+--------------+
| 1 | root | 123456 | 18 | 男 | admin@ad.com |
| 2 | admin | 123 | 18 | 男 | admin@ad.com |
| 3 | admin | 123 | 18 | 男 | admin@ad.com |
| 4 | admin | 123 | 18 | 男 | admin@ad.com |
| 5 | admin | 123 | 18 | 男 | admin@ad.com |
| 6 | admin | 123 | 18 | 男 | admin@ad.com |
+----+-----------+----------+------+--------+--------------+
6 rows in set (0.00 sec)
6、简单delete
UserMapper接口文件中
/*** 删除一条用户数据* @return int Sql执行完成后影响的行数*/int deleteUser();
UserMapper.xml映射文件中
<delete id="deleteUser">delete from t_user where id=5;
</delete>
Test类中:
@Testpublic void testDelete(){SqlSession sqlSession=SqlsessionUtil.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);int result = mapper.deleteUser();System.out.println("result="+result);System.out.println(result==1 ? "添加成功":"添加失败");sqlSession.close();}
结果:
DEBUG 08-07 17:25:16,324 ==> Preparing: delete from t_user where id=5;(BaseJdbcLogger.java:137)
DEBUG 08-07 17:25:16,359 ==> Parameters: (BaseJdbcLogger.java:137)
DEBUG 08-07 17:25:16,363 <== Updates: 1(BaseJdbcLogger.java:137)
result=1
添加成功mysql> select * from t_user;
+----+-----------+----------+------+--------+--------------+
| id | user_name | password | age | gender | email |
+----+-----------+----------+------+--------+--------------+
| 1 | root | 123456 | 18 | 男 | admin@ad.com |
| 2 | admin | 123 | 18 | 男 | admin@ad.com |
| 3 | admin | 123 | 18 | 男 | admin@ad.com |
| 4 | admin | 123 | 18 | 男 | admin@ad.com |
| 6 | admin | 123 | 18 | 男 | admin@ad.com |
+----+-----------+----------+------+--------+--------------+
5 rows in set (0.00 sec)
7、简单select
使用ID查询单个用户信息
注意,与另外两个返回值为int的SQL语句不同,select需要配置一个ResultMap来定义类属性和数据库表字段中的关系:
UserMapper接口类
/*** 根据id获取用户信息* @return User user的信息*/User getUserById();
UserMaper.xml映射文件
resultType:指定查询出来的数据需要转换成的Java对象
resultMap:通常用来制定一对多的关系
<resultMap id="UserResult" type="com.lobo.entity.User"><id property="id" column="id" /><result column="user_name" property="userName" /><result column="password" property="password" /><result column="age" property="age" /><result column="gender" property="gender" /><result column="email" property="email" /></resultMap><select id="getUserById" resultMap="UserResult">select * from t_user where id =1;</select>可以使用指定resultType的方式,不用写resultMap
但是数据库字段名称与Java类中的属性名称必须相同<select id="getUserById" resultType="com.lobo.entity.User">select * from t_user where id =1;</select>以上resultType和resultMap不能都没有,但是也不能同时存在
注意其中的column和property的区别:
column:指的是表的字段名称
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pKEjeHgW-1690788149165)(Mybatis.assets/image-20220807174620881.png)]
property:指的是对应实体类中的属性名称:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zOzcoLDk-1690788149170)(Mybatis.assets/image-20220807174651937.png)]
测试结果:
log4j: Adding appender named [STDOUT] to category [root].
DEBUG 08-07 17:42:24,382 ==> Preparing: select * from t_user where id =1;(BaseJdbcLogger.java:137)
DEBUG 08-07 17:42:24,419 ==> Parameters: (BaseJdbcLogger.java:137)
DEBUG 08-07 17:42:24,450 <== Total: 1(BaseJdbcLogger.java:137)
User(id=1, userName=root, password=123456, age=18, gender=男, email=admin@ad.com)以上是使用ResultMap方法执行之后的结果log4j: Adding appender named [STDOUT] to category [root].
DEBUG 08-07 17:55:07,444 ==> Preparing: select * from t_user where id =1;(BaseJdbcLogger.java:137)
DEBUG 08-07 17:55:07,483 ==> Parameters: (BaseJdbcLogger.java:137)
DEBUG 08-07 17:55:07,511 <== Total: 1(BaseJdbcLogger.java:137)
User(id=1, userName=null, password=123456, age=18, gender=男, email=admin@ad.com)以上是使用ResultType的方法执行之后的结果
可以发现由于数据库中的字段名为user_name,而java实体类中的名称为userName,没有匹配上,所以userName为null所以我们更改一下User类中的属性来验证一下我们的猜想:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bQPdBF64-1690788149174)(Mybatis.assets/image-20220807175747618.png)]
执行结果:
DEBUG 08-07 17:58:04,678 ==> Preparing: select * from t_user where id =1;(BaseJdbcLogger.java:137)
DEBUG 08-07 17:58:04,713 ==> Parameters: (BaseJdbcLogger.java:137)
DEBUG 08-07 17:58:04,744 <== Total: 1(BaseJdbcLogger.java:137)
User(id=1, user_name=root, password=123456, age=18, gender=男, email=admin@ad.com)Process finished with exit code 0
结果才可以查询到user_name字段
那就把数据库字段改为username吧…
alter table t_user change user_name username varchar(20);
然后把实体类改回来
记得把之前的sql语句名称更改一下
查询所有用户信息
/*** 查询所有的用户信息* @return List<User>所有用户的信息列表*/List<User> getAlluser();
<select id="getAlluser" resultType="com.lobo.entity.User">select * from t_user;</select>
@Testpublic void testSelectAllUser(){SqlSession sqlSession=SqlsessionUtil.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> user = mapper.getAlluser();user.forEach(System.out::println);}//虽然返回的是List,但是其中的类型还是User,所以使用resultType来指定为User即可
8、Mybatis配置文件核心配置项
<?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="db-source-config.properties" /><!-- The content of element type "configuration" must match"(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)".--><!-- 设置类型的别名--><!-- Mybatis的核心配置文件中的标签必须要按照制定的顺序配置 --><typeAliases>
<!-- 通过别名User就可以访问com.lobo.entity.User包-->
<!-- 可以查看UserMapper.xml中用ID查询用户的的resultType--><typeAlias type="com.lobo.entity.User" alias="UserAAA" /><!-- 以包的方式设置别名-->
<!-- 其包中的所有子类,以类名的方式作为别名-->
<!-- 查看UserMapper.xml中查询所有用户的resultType--><package name="com.lobo.entity"/></typeAliases><!-- environments 配置连接数据库的环境 默认:development--><!-- default指定当前使用的环境配置--><environments default="development">
<!-- environment是具体的环境配置,id不可重复,但是可以有多个环境-->
<!-- 多个环境只能有一个作为当前环境--><environment id="development">
<!-- 事务管理器 type:设置事务管理的方式-->
<!-- type=JDBC|MANAGED-->
<!-- JDBC:表示使用JDBC中原生的事务管理方式-->
<!-- MANAGED:被管理,例如被Spring管理--><transactionManager type="JDBC" />
<!-- 数据源-->
<!-- 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><environment id="test"><transactionManager type="JDBC" /><!-- 数据源--><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的映射文件--><mappers>
<!-- 引入单个Mybatis的xml映射配置文件-->
<!-- <mapper resource="mappers/UserMapper.xml" />--><!-- 通过包名将某个包下面的所有mapper文件进行引入,但是要注意以下几个问题:-->
<!-- 类名与xml配置文件的名称相同-->
<!-- mapper接口和映射文件所在的包必须一致-->
<!-- mapper接口的名字和映射文件的名字必须一致--><package name="com.lobo.mapper"/>配置完毕之后一定要看下面两张图,完成结构修改</mappers></configuration>---------------------修改xml映射文件中的部分代码:<select id="getUserById" resultType="UserAAA">select * from t_user where id =1;</select><select id="getAlluser" resultType="User">select * from t_user;</select>
修改mapper映射文件的位置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ginjMJ7l-1690788149179)(Mybatis.assets/image-20220807185612978.png)]
为什么这样修改能使得包名作为Mapper目录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sN7BYiaF-1690788149184)(Mybatis.assets/image-20220807185800130.png)]
生成target之后可以看到原本的mapper包和下面的mapper文件夹合并了
最终是加载到了同一个文件夹下
9、创建类型模板
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XHrYpOJK-1690788149188)(Mybatis.assets/image-20220808092421497.png)]
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><!--添加properties配置文件路径(外部配置、动态替换)--><properties resource="db-source-config.properties" /><!-- The content of element type "configuration" must match"(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)".--><!-- 设置类型的别名--><typeAliases><typeAlias type="" alias="" /><!-- 以包的方式设置别名--><package name=""/></typeAliases><!-- environments 配置连接数据库的环境 默认:development--><environments default="development">
<!-- environment是具体的环境配置,id不可重复,但是可以有多个环境-->
<!-- 多个环境只能有一个作为当前环境--><environment id="development">
<!-- 事务管理器 type:设置事务管理的方式-->
<!-- type=JDBC|MANAGED-->
<!-- JDBC:表示使用JDBC中原生的事务管理方式-->
<!-- MANAGED:被管理,例如被Spring管理--><transactionManager type="JDBC" />
<!-- 数据源-->
<!-- 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><!-- 引入mybatis的映射文件--><mappers><package name=""/></mappers>
</configuration>
*Mapper.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=""><select id="" resultMap=""></select>
</mapper>
10、MyBatis获取参数值方式
在映射文件中将参数作为输入值的两种方式:
分为:${} 和 #{}
${} 的本质就是字符串拼接,而 #{} 的本质就是占位符赋值
${} 使用字符串拼接的方式拼接SQL,此时为字符串类型或者日期类型的字段进行赋值时需要手动添加单引号
拼接字符串的SQL是很危险的。。。
10.1、单个字面量类型的参数
若Mapper接口中的方法参数为单个字面值类型,此时${} 与 #{}都可以完成赋值
mybatis会将参数放到一个map集合中,有两种存储方式:arg和param,存放如arg0、arg1…参数argN 和 param1,param2…paramN
10.1.1、如何使用
单个自变量:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f7kKD7FS-1690788149193)(Mybatis.assets/image-20220808142848691.png)]
这里的username=#{abcd}依然可以运行,因为mybatis将其转换为了param1,只有一个参数,所以编译运行成功。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C6eak33B-1690788149198)(Mybatis.assets/image-20220808142754092.png)]
DEBUG 08-08 14:25:46,133 ==> Preparing: select id,username,password,age,gender,email from t_user where username= ?(BaseJdbcLogger.java:137)
DEBUG 08-08 14:25:46,162 ==> Parameters: root(String)(BaseJdbcLogger.java:137)
DEBUG 08-08 14:25:46,191 <== Total: 1(BaseJdbcLogger.java:137)
root==User(id=1, username=root, password=123456, age=18, gender=男, email=admin@ad.com)
如果使用${}:
若使用${} 在使用时,若是字符串型变量,则需要加''号。如:
select id,username,password,age,gender,email from t_user where username= '${username}'
10.2、多个字面量类型的参数
若mapper接口中的方法参数为多个时
Mybatis会自动将这些参数放入Map集合中,以arg0,arg1…为关键字,以param1,param2参数为值
10.2.1、如何使用
首先我们定义接口:
/*** 根据账号密码登录* @param username 用户名* @param password 密码* @return 返回查询到的User对象*/
int checkLogin(String username,String password);
定义mapper映射文件中的方法:
<select id="checkLogin" resultType="Integer">select id,username,password,age,gender,email from t_user where username = #{username} and password= #{password};</select>
我们这填的是参数中的username和password,看起来没有问题
但是其实是有问题的
我们编写测试类进行测试:
@Testpublic void testLogin(){SqlSession sqlSession=SqlsessionUtil.getSqlSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);int result = userMapper.checkLogin("root", "123456");System.out.println(result==1?"登录成功":"登陆失败");}
结果:
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter 'username' not found. Available parameters are [arg1, arg0, param1, param2]
### Cause: org.apache.ibatis.binding.BindingException: Parameter 'username' not found. Available parameters are [arg1, arg0, param1, param2]
注意这里报出的错误:绑定的参数username未找到,可用的参数有:arg1,arg2,param1,param2
这里出现了mybatis的参数绑定错误(因为mybatis不知道你这个参数为什么名字,详情见上一个单字面量类型参数。
我们就遵循mybatis的规范,我们将参数映射文件中的 username 和 password 更改为param1,param2或者是arg0,arg1
<select id="checkLogin" resultType="Integer">select id,username,password,age,gender,emailfrom t_userwhere username = #{param1} and password= #{param2};</select>
若使用${},要注意添加单引号
此时我们查看结果就是正确的了:
DEBUG 08-08 15:21:25,979 ==> Preparing: select id,username,password,age,gender,email from t_user where username = ? and password= ?;(BaseJdbcLogger.java:137)
DEBUG 08-08 15:21:26,065 ==> Parameters: root(String), 123456(String)(BaseJdbcLogger.java:137)
DEBUG 08-08 15:21:26,093 <== Total: 1(BaseJdbcLogger.java:137)
登录成功
10.3、使用map键值来进行参数输入
创建接口方法:
/*** 根据Map中的键值来作为参数,验证密码登录* @param map HashMap,对应有username和password两个键* @return 返回查询到的User个数*/int checkLoginByMap(Map<String,Object> map);
将输入的参数设置为Map键值对类型
然后在测试类中,定义好我们的键值对
@Test
public void testLoginByMap(){SqlSession sqlSession=SqlsessionUtil.getSqlSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);Map<String, Object> map = new HashMap<>();map.put("username","root");map.put("password","123456");int result = userMapper.checkLoginByMap(map);System.out.println(result==1?"登录成功":"登陆失败");
}
此时我们有两个键username和password,然后我们到xml映射文件中定义SQL语句,其中我们不再使用param1、param2这种mybatis为我们定义的键值,我们选择使用我们自己定义的键值对作为参数:
<select id="checkLoginByMap" resultType="Integer">select id,username,password,age,gender,emailfrom t_userwhere username = #{username} and password= #{password};</select>
结果:
DEBUG 08-08 15:32:23,998 ==> Preparing: select id,username,password,age,gender,email from t_user where username = ? and password= ?;(BaseJdbcLogger.java:137)
DEBUG 08-08 15:32:24,038 ==> Parameters: root(String), 123456(String)(BaseJdbcLogger.java:137)
DEBUG 08-08 15:32:24,078 <== Total: 1(BaseJdbcLogger.java:137)
登录成功
10.4、使用对象进行参数输入
这里我们的User对象中的属性值(可以被getter/setter访问的)就类似于键值对,其中的各个属性对应相应的键,其中的值,就可以作为参数进入SQL执行
我们先创建接口方法:
/*** 将User作为参数,添加用户信息* @param user 要添加的User对象* @return Sql影响的行数*/int insertUserByObjectUser(User user);
然后根据上面的结论编写SQL映射文件
<insert id="insertUserByObjectUser">insert into t_user(username,password,age,gender,email)values (#{username},#{password},#{age},#{gender},#{email});</insert>
注意这里使用的是#{ user中的变量名 }
编写测试类:
@Testpublic void testLoginByObjectUser(){SqlSession sqlSession=SqlsessionUtil.getSqlSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);User user=new User();user.setUsername("wuxidixi");user.setPassword("123");user.setAge(18);user.setGender("男");user.setEmail("1111@111.com");int result = userMapper.insertUserByObjectUser(user);System.out.println(result==1?"插入成功":"插入失败");sqlSession.close();}
运行结果:
DEBUG 08-08 15:48:05,523 ==> Preparing: insert into t_user(username,password,age,gender,email) values (?,?,?,?,?);(BaseJdbcLogger.java:137)
DEBUG 08-08 15:48:05,553 ==> Parameters: wuxidixi(String), 123(String), 18(Integer), 男(String), 1111@111.com(String)(BaseJdbcLogger.java:137)
DEBUG 08-08 15:48:05,557 <== Updates: 1(BaseJdbcLogger.java:137)
插入成功
插入成功,我们查看数据库:(我插入了两次)
mysql> select * from t_user;
+----+----------+----------+------+--------+--------------+
| id | username | password | age | gender | email |
+----+----------+----------+------+--------+--------------+
| 1 | root | 123456 | 18 | 男 | admin@ad.com |
| 2 | admin | 123 | 18 | 男 | admin@ad.com |
| 3 | admin | 123 | 18 | 男 | admin@ad.com |
| 4 | admin | 123 | 18 | 男 | admin@ad.com |
| 6 | admin | 123 | 18 | 男 | admin@ad.com |
| 7 | wuxidixi | 123 | 18 | 男 | 1111@111.com |
| 8 | wuxidixi | 123 | 18 | 男 | 1111@111.com |
+----+----------+----------+------+--------+--------------+
7 rows in set (0.00 sec)
10.5、使用注解的方式进行参数输入
/*** 使用注解的方式* @param username 使用了@Param注解的username* @param password 使用了@Param注解的password* @return Sql影响的行数*/
int checkLoginByParam(@Param("username")String username,@Param("password") String password);
<select id="checkLoginByParam" resultType="Integer">select id,username,password,age,gender,emailfrom t_userwhere username = #{username} and password= #{password};</select>
@Testpublic void testLoginByAnnotation(){SqlSession sqlSession=SqlsessionUtil.getSqlSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);int result = userMapper.checkLoginByParam("root","123456");System.out.println(result==1?"登录成功":"登陆失败");sqlSession.close();}
11、mybatis的各种查询
sqlSession会根据我们接口类中的返回对象来调用对应的方法,如selectOne(),selectList()
若使用selectOne()却查询出了多条语句,则会显示:
TooManyResultException 过多返回值
11.1、简单查询
11.1.1、查询某个实体类对象
User getUserByUserName(String username);
<select id="getUserByUserName" resultType="User">select id,username,password,age,gender,email from t_user where username= #{username}
</select>
11.1.2、查询一个list集合
List<User> getAlluserAsList();
<select id="getAlluserAsList" resultType="User">select id,username,password,age,gender,emailfrom t_user;</select>
@Testpublic void testGetAllUserAsList(){SqlSession sqlSession=SqlsessionUtil.getSqlSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);List<User> alluserAsList = userMapper.getAlluserAsList();alluserAsList.forEach(System.out::println);sqlSession.close();}
11.1.3、查询一个具体值
mybatis中为Java中常用的类型设置了类型的别名
Integer:Integer,int
int:_int ,_integer
Map:map
String:string
/*** 获取用户的数量* @return int 用户的数量*/int getUserCount();
<select id="getUserCount" resultType="Integer">select count(id) from t_user ;</select>
@Test
public void getUserCountTest(){SqlSession sqlSession= SqlsessionUtil.getSqlSession();SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);int userCount = mapper.getUserCount();System.out.println(userCount>0? "userCount="+userCount:"没有任何用户");sqlSession.close();
}
11.1.4、查询一个Map集合
/*** 根据用户的ID查询用户信息,返回MAP集合* @param id 用户id* @return 用户的信息*/Map<String, Object> getUserByIdToMap(@Param("id")int id);
<select id="getUserByIdToMap" resultType="map">select id,username,password,age,gender,emailfrom t_userwhere id = #{id}</select>
@Testpublic void getUserByIdAsMapTest(){SqlSession sqlSession= SqlsessionUtil.getSqlSession();SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);Map<String, Object> userByIdToMap = mapper.getUserByIdToMap(1);System.out.println(userByIdToMap);sqlSession.close();}
结果:
如果字段的内容为NULL,则不会讲该字段放入Map集合中
log4j: Adding appender named [STDOUT] to category [root].
DEBUG 08-08 17:37:25,768 ==> Preparing: select id,username,password,age,gender,email from t_user where id = ?(BaseJdbcLogger.java:137)
DEBUG 08-08 17:37:25,798 ==> Parameters: 1(Integer)(BaseJdbcLogger.java:137)
DEBUG 08-08 17:37:25,821 <== Total: 1(BaseJdbcLogger.java:137)
{password=123456, gender=男, id=1, age=18, email=admin@ad.com, username=root}
11.1.5、将多条数据放入Map
第一种方式:
/*** 查询所有的用户信息为一个Map集合* @return 所有用户信息的Map集合*/// Map<String,Object> getAllUserToMap(); 不能将返回值设置为Map因为返回值有多条
List<Map<String,Object>> getAllUserToMap(); //应该返回List<Map>对象存储多条值
<select id="getAllUserToMap" resultType="map">select id,username,password,age,gender,emailfrom t_user;
</select>
@Testpublic void getAllUserToMapTest(){SqlSession sqlSession= SqlsessionUtil.getSqlSession();SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);List<Map<String, Object>> allUserToMap = mapper.getAllUserToMap();allUserToMap.forEach(System.out::println);sqlSession.close();}
}
第二种方式:
/*** 用不同的键值来存储不同的用户(用Map存储整个信息)* @return Map集合*/
@MapKey("id") //使用注解指定键值
Map<String,Object> getAllUserToMap();//注意这里的返回类型,与第一种不同
<select id="getAllUserToMap" resultType="map">select id,username,password,age,gender,emailfrom t_user;
</select>
@Testpublic void getAllUserToMapTest(){SqlSession sqlSession= SqlsessionUtil.getSqlSession();SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);Map<String, Object> allUserToMap = mapper.getAllUserToMap();System.out.println(allUserToMap);sqlSession.close();}
结果:
log4j: Adding appender named [STDOUT] to category [root].
DEBUG 08-08 17:51:28,115 ==> Preparing: select id,username,password,age,gender,email from t_user;(BaseJdbcLogger.java:137)
DEBUG 08-08 17:51:28,146 ==> Parameters: (BaseJdbcLogger.java:137)
DEBUG 08-08 17:51:28,175 <== Total: 7(BaseJdbcLogger.java:137)
##map集合,使用@MapKey指定的参数为键,查询出的对象为值
{1={password=123456, gender=男, id=1, age=18, email=admin@ad.com, username=root}, 2={password=123, gender=男, id=2, age=18, email=admin@ad.com, username=admin}, 3={password=123, gender=男, id=3, age=18, email=admin@ad.com, username=admin}, 4={password=123, gender=男, id=4, age=18, email=admin@ad.com, username=admin}, 6={password=123, gender=男, id=6, age=18, email=admin@ad.com, username=admin}, 7={password=123, gender=男, id=7, age=18, email=1111@111.com, username=wuxidixi}, 8={password=123, gender=男, id=8, age=18, email=1111@111.com, username=wuxidixi}}
11.2、特殊查询
11.2.1、模糊查询
/*** 模糊查询用户姓名* @param username 模糊的用户名* @return 查询到的用户*/
List<User> getUserByNameWhereLike(@Param("username")String username);
<!-- List<User> getUserByNameWhereLike(@Param("username")String username);--><!-- 第一种方式--><!-- select id,username,password,age,gender,email--><!-- from t_user--><!-- where username like '%${username}%'--><!-- 第二种方式--><!-- select id,username,password,age,gender,email--><!-- from t_user--><!-- where username like concat('%',#{username},'%');-->‘<!-- 第三种方式--><!-- select id,username,password,age,gender,email--><!-- from t_user--><!-- where username like "%"#{username}"%";--><!-- List<User> getUserByNameWhereLike(@Param("username")String username);--><select id="getUserByNameWhereLike" resultType="User">select id,username,password,age,gender,email from t_userwhere username like "%"#{username}"%"</select>
注意,这里的第一种映射文件中不能使用 #{ } 的方式进行赋值,因为Mybatis在编译#{}的时候,会将SQL进行预编译,而 ’ ’ 两个单引号将 %${username}% 变为了字符串,此时的?不再是占位符的意思,所以Mybatis会报错,Parameter index out of range
@Testpublic void likeSQLTest(){SqlSession sqlSession= SqlsessionUtil.getSqlSession();SpecialSQLMapper mapper = sqlSession.getMapper(SpecialSQLMapper.class);List<User> users = mapper.getUserByNameWhereLike("r");users.forEach(System.out::println);sqlSession.close();}
12、批量删除
//声明接口:/*** 批量删除用户(字符串拆解方式)* @param ids 多个用户的ID,用,连接* @return SQL影响的条数*/
int deleteMultipleUserById(@Param("ids")String ids);
<!-- int deleteMultipleUserById(@Param("ids")String ids);-->
<!-- ids:1,2,3,4,5,6--><delete id="deleteMultipleUserById">delete from t_user where id in(${ids});</delete>
注意:以上不能使用 #{ } 方式,因为采用 #{ } 方式会自动在ids的参数外面加两个单引号,如:
我们使用${ }传入: 1,2,3,4 则正确语句为 where id in(1,2,3,4)
若使用#{ }: where id in (‘1,2,3,4’),此时,程序就无法编译执行。
@Test
public void deleteMultipleUser(){SqlSession sqlSession= SqlsessionUtil.getSqlSession();SpecialSQLMapper mapper = sqlSession.getMapper(SpecialSQLMapper.class);int result = mapper.deleteMultipleUserById("7,8");System.out.println(result>0?"删除完成":"未删除任何数据");sqlSession.close();
}
13、动态设置表名
/*** 动态表名获取用户信息* 使用场景:有相同表内容的user表和vip_user表* @param tableName 表名* @return 该表的所有用户*/List<User> getAllUser(@Param("tableName")String tableName);
<select id="getAllUser" resultType="User">select id,username,password,age,gender,email from ${tableName}
</select>
这里跟之前的方法是一样的,#{}依然会给表名添加两个单引号,所以虽然能执行(也不一定会执行),但是查不到数据,所以一定要选择${}的方式拼接字符串,才能查到。
@Testpublic void selectUserFromDiffTableTest(){SqlSession sqlSession= SqlsessionUtil.getSqlSession();SpecialSQLMapper mapper = sqlSession.getMapper(SpecialSQLMapper.class);List<User> users = mapper.getAllUser("t_user");users.forEach(System.out::println);sqlSession.close();}
14、添加功能获取自增的主键
14.1、使用JDBC的方式
@Test
public void getAutoIncrementIDByJDBC(){Connection connection=null;PreparedStatement preparedStatement=null;ResultSet resultSet=null;ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbc-config");String driverName=resourceBundle.getString("jdbc.driver");String url=resourceBundle.getString("jdbc.url");String user=resourceBundle.getString("jdbc.username");String password=resourceBundle.getString("jdbc.password");try {Class.forName(driverName);connection= DriverManager.getConnection(url,user,password);String sql="insert into t_user(username,password,age,gender ,email) values(?,?,?,?)";//增加参数,使得可以获取自增ID 2:不允许,1:允许//使用自变量 Statement.RETURN_GENERATED_KEYSpreparedStatement = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);preparedStatement.setString(1,"makabaka");preparedStatement.setString(2,"123");preparedStatement.setInt(3,22);preparedStatement.setString(4,"女");int excuteResult = preparedStatement.executeUpdate();System.out.println(excuteResult==1?"插入成功":"插入失败");//获取插入的自增IDresultSet = preparedStatement.getGeneratedKeys();resultSet.next();int result=resultSet.getInt(1);System.out.println("resultSet.getInte(1)===>"+result);} catch (ClassNotFoundException |SQLException e) {e.printStackTrace();}finally{if (connection!=null){try {connection.close();} catch (SQLException e) {e.printStackTrace();}}if (preparedStatement!=null){try {preparedStatement.close();} catch (SQLException e) {e.printStackTrace();}}if (resultSet!=null){try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}}
}
注意这里使用的
preparedStatement = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
这条语句后面这个参数可以直接跟数字,其中1是开启返回自增的主键,2是关闭,但是直接填写数字会影响代码的可读性,所以使用Statement中提供的枚举类型来代替纯数字,提高代码可读性。
还有就是这后面这串代码:
int excuteResult = preparedStatement.executeUpdate();
System.out.println(excuteResult==1?"插入成功":"插入失败");//获取插入的自增ID
resultSet = preparedStatement.getGeneratedKeys();
resultSet.next();
int result=resultSet.getInt(1);
System.out.println("resultSet.getInte(1)===>"+result);
这里excuteResult返回的是上一条插入语句返回的result值,也就是insert 这条SQL语句的影响行数。
然后使用preparedStatement.getGeneratedKeys();获取自增的主键,然后将返回值给resultset
resultset将光标跳到下一行,有且只有一行的resultset中包含只包含一个数据,就是insert语句之后获取到的自增主键。
执行结果:
插入成功 这里返回的是1,是SQL影响的行数
resultSet.getInte(1)===>11 这是获取插入后的自增主键的值,这里是插入的第11个用户
Process finished with exit code 0
14.2、使用Mybatis的方式
/*** 添加一条用户信息,并获取自增主键* @param user 用户对象,里面包含用户的信息* @return 返回执行SQL之后影响的行数*/int insertUser(User user);
<!-- int insertUser(User user);-->
<!-- 需要用到两个属性:useGeneratedKeys:开启初始化的自增字段,存储到实体类的id中--><insert id="insertUser" useGeneratedKeys="true" keyProperty="id">insert into t_user(username,password,age,gender ,email) values(#{username},#{password},#{age},#{gender},#{email});</insert>
一定要开启上两个属性:useGeneratedKeys=true,因为使用的是insert语句,Mybatis的insert语句返回值只有int和void,一个是返回SQL执行后影响的行数,还有一个是不返回值。
所以我们要获取其中返回的自增的值的话,我们只能将属性放到我们的user对象当中,但是要放到对象中的那个位置Mybatis并不清楚,所以我们需要指定返回的key属于哪个字段。
keyProperty="id"
执行结果如下:(对比两个User的信息,发现插入完成之后,User的id字段被自动填充了)
User(id=null, username=helloWorld1, password=123, age=23, gender=女, email=abc@abc.com)
DEBUG 08-09 10:02:32,724 ==> Preparing: insert into t_user(username,password,age,gender ,email) values(?,?,?,?,?);(BaseJdbcLogger.java:137)
DEBUG 08-09 10:02:32,756 ==> Parameters: helloWorld1(String), 123(String), 23(Integer), 女(String), abc@abc.com(String)(BaseJdbcLogger.java:137)
DEBUG 08-09 10:02:32,769 <== Updates: 1(BaseJdbcLogger.java:137)
插入成功
执行之后的user对象
User(id=12, username=helloWorld1, password=123, age=23, gender=女, email=abc@abc.com)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l6O8Vqzv-1690788149202)(Mybatis.assets/image-20220809100405700.png)]
15、自定义映射resultMap
15.1、处理字段和属性的映射关系
创建对应的表:
drop table if exists t_dept;
create table t_dept(dept_id int(10) primary key auto_increment,dept_name varchar(20)
);
insert into t_dept(dept_name)values('A');
insert into t_dept(dept_name)values('B');
insert into t_dept(dept_name)values('C');
insert into t_dept(dept_name)values('D');drop table if exists t_emp;
create table t_emp(emp_id int(10) primary key auto_increment,emp_name varchar(20),age int(4),gender char(5),dept_id int not null
);
insert into t_emp(emp_name,age,gender,dept_id)values('makabkaa',18,'男',1);
insert into t_emp(emp_name,age,gender,dept_id)values('wuxidixi',18,'女',2);
insert into t_emp(emp_name,age,gender,dept_id)values('tom',19,'男',1);
insert into t_emp(emp_name,age,gender,dept_id)values('jack',20,'男',1);
insert into t_emp(emp_name,age,gender,dept_id)values('lucy',21,'男',2);
mysql> select * from t_dept;
+---------+-----------+
| dept_id | dept_name |
+---------+-----------+
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | D |
+---------+-----------+
4 rows in set (0.00 sec)mysql> select * from t_emp;
+--------+----------+------+--------+---------+
| emp_id | emp_name | age | gender | dept_id |
+--------+----------+------+--------+---------+
| 1 | makabkaa | 18 | 男 | 1 |
| 2 | wuxidixi | 18 | 女 | 2 |
| 3 | tom | 19 | 男 | 1 |
| 4 | jack | 20 | 男 | 1 |
| 5 | lucy | 21 | 男 | 2 |
+--------+----------+------+--------+---------+
5 rows in set (0.01 sec)
实体类:
package com.lobo.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** className: Dept<br/>* author: MacieSerenity <br/>* date: 2022-08-09 10:39**/
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Dept {/*** 部门ID*/private int deptId;/*** 部门名称*/private String deptName;
}---------------------------------------package com.lobo.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** className: Emp<br/>* author: MacieSerenity <br/>* date: 2022-08-09 10:37**/
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Emp {/*** 用户ID*/private int empId;/*** 用户姓名*/private String empName;/*** 用户年龄*/private int age;/*** 用户性别*/private String gender;/*** 部门ID*/private int deptId;
}
一般我们创建Java实体类时,和数据库的字段,由于两边的命名规则不同(数据库不分大小写,而Java要分)例如Java中我们习惯使用驼峰命名属性,但是由于数据库不分大小写,我们通常使用下划线作为分隔符分割两个单词,此时的数据库名称和Java中的名称不一致,我们需要配置两边的映射关系。
第一种方式:设置列别名
<!-- 采用别名的方式:select emp_id empId,emp_name empName,age,gender,dept_id where emp_id = #{empId}; (此时是resultType:Emp)-->
第二种方式:配置mybatis-config.xml全局变量
(前提是必须满足java以驼峰命名,数据库下划线分割单词)
例如:数据库中 emp_id 对应 java中 empId 数据库中 emp_name 对应 java中 empName
配置该语句的意思就是将下划线转为下一个字母的大写:
<settings><!-- 将下划线映射为驼峰--><setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
注意:要注意setting放置的位置(当前是在和之间),要是放错了会报错。
DEBUG 08-09 11:25:08,298 ==> Preparing: select emp_id,emp_name,age,gender,dept_id from t_emp where emp_id = ?;(BaseJdbcLogger.java:137) DEBUG 08-09 11:25:08,344 ==> Parameters: 1(Integer)(BaseJdbcLogger.java:137) DEBUG 08-09 11:25:08,389 <== Total: 1(BaseJdbcLogger.java:137) Emp(empId=1, empName=makabkaa, age=18, gender=男, deptId=1)
第三种方式:使用resultMap
要注意resultMap=某个配置好了的<resultMap id=“ ”>的ID
resultMap内部标签含义:
id:指定此字段为id
collection:集合,一般使用一对一查询
association:关联,一般使用一对多查询
<resultMap id="EmpMap" type="com.lobo.entity.Emp"><id column="emp_id" property="empId" /><result column="emp_name" property="empName" /><result column="age" property="age" /><result column="gender" property="gender" /><result column="dept_id" property="deptId" /></resultMap>
<!-- Emp getEmpByEmpId(@Param("empId")int empId);-->
<select id="getEmpByEmpId" resultMap="EmpMap">select emp_id,emp_name,age,gender,dept_id from t_empwhere emp_id = #{empId};
</select>
注意这里的select标签中使用的是resultMap属性,指定的是resultMap标签中的id属性。
DEBUG 08-09 11:25:08,298 ==> Preparing: select emp_id,emp_name,age,gender,dept_id from t_emp where emp_id = ?;(BaseJdbcLogger.java:137) DEBUG 08-09 11:25:08,344 ==> Parameters: 1(Integer)(BaseJdbcLogger.java:137) DEBUG 08-09 11:25:08,389 <== Total: 1(BaseJdbcLogger.java:137) Emp(empId=1, empName=makabkaa, age=18, gender=男, deptId=1)
15.2、处理多对一级联映射关系Association
我们需要在对应的多对一中的多的数据实体类中添加一个属性用于存储查询出的结果
更改一下Emp类,往其中添加一个Dept对象来存储部门的信息
package com.lobo.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;/*** className: Emp<br/>* author: MacieSerenity <br/>* date: 2022-08-09 10:37**/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Emp {/*** 用户ID*/private int empId;/*** 用户姓名*/private String empName;/*** 用户年龄*/private int age;/*** 用户性别*/private String gender;/*** 部门ID*/private int deptId;/*** 使用实体类来存储*/private Dept dept;
}
第一种方式:对象.属性
/*** 根据EmpID查询Emp和Detp* @param empId 用户的ID* @return 返回EMP对象*/Emp getEmpAndDeptByEmpId(@Param("empId")int empId);
<resultMap id="empAndDeptMap" type="Emp"><id column="emp_id" property="empId" /><result column="emp_name" property="empName" /><result column="age" property="age" /><result column="gender" property="gender" /><result column="dept_id" property="deptId" /><result column="dept_id" property="dept.deptId" /><result column="dept_name" property="dept.deptName" />
</resultMap>
<!-- Emp getEmpAndDeptByEmpId(@Param("empId")int empId);--><select id="getEmpAndDeptByEmpId" resultMap="empAndDeptMap">select emp_id,emp_name,age,gender,t_emp.dept_id,dept_namefrom t_emp left join t_dept on t_emp.dept_id=t_dept.dept_idwhere emp_id = #{empId};</select>
需要注意的就是这里我把原本Emp对象中的deptId给取消了,换成了Dept对象,我们在配置resultMap的时候将property指定为该对象的属性即可,方法是跟java调用属性方法一样,用点号跟属性。
@Test
public void testGetEmpAndDeptById(){SqlSession sqlSession= SQLSessionUtil.getSQLSession();EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);Emp emp = mapper.getEmpAndDeptByEmpId(1);System.out.println(emp.toString());sqlSession.close();
}
结果:
DEBUG 08-09 15:19:01,459 ==> Preparing: select emp_id,emp_name,age,gender,t_emp.dept_id,dept_name from t_emp left join t_dept on t_emp.dept_id=t_dept.dept_id where emp_id = ?;(BaseJdbcLogger.java:137)
DEBUG 08-09 15:19:01,501 ==> Parameters: 1(Integer)(BaseJdbcLogger.java:137)
DEBUG 08-09 15:19:01,584 <== Total: 1(BaseJdbcLogger.java:137)
Emp(empId=1, empName=makabkaa, age=18, gender=男, deptId=1, dept=Dept(deptId=1, deptName=A))
第二种方式:关联 association
只需要将ResultMap改为:
associationL处理多对一的映射关系,处理实体类类型的属性
javaType:设置要处理的属性的类型
<resultMap id="empAndDeptMapAssociation" type="Emp"><id column="emp_id" property="empId" /><result column="emp_name" property="empName" /><result column="age" property="age" /><result column="gender" property="gender" /><result column="dept_id" property="deptId" /><association property="dept" javaType="Dept"><id column="dept_id" property="deptId" /><result column="dept_name" property="deptName"/></association>
</resultMap>
只需要更改resultMap,查询的结果相同
第三种方式:分步查询
第三种方式属于是不同Mapper中的复用
我们先创建DeptMapper
package com.lobo.mapper;import com.lobo.entity.Dept;
import org.apache.ibatis.annotations.Param;/*** className: DeptMapper<br/>* author: MacieSerenity <br/>* date: 2022-08-09 15:23**/
public interface DeptMapper {/*** 根据DeptID查询Dept* @param deptId Dept的id* @return Dept对象*/Dept getDeptByDeptId(@Param("deptId")int deptId);
}
DeptMapper.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="com.lobo.mapper.DeptMapper">
<!-- Dept getDeptByDeptId(@Param("deptId")int deptId);--><select id="getDeptByDeptId" resultType="Dept">select dept_id,dept_name from t_dept where dept_id=#{deptId};</select>
</mapper>
然后我们在EmpMapper中调用方法
详细使用方式
<resultMap id="empAndDeptMapStep" type="Emp"><id column="emp_id" property="empId" /><result column="emp_name" property="empName" /><result column="age" property="age" /><result column="gender" property="gender" /><result column="dept_id" property="deptId" /><associationproperty="dept"select="com.lobo.mapper.DeptMapper.getDeptByDeptId"column="dept_id"></association>
</resultMap>
可以看到上面虽然也是用association标签,但是写法有一点不同
property:指定返回的值对应Java实体类的属性
select:指定使用的方法,将该方法查询到的返回值返回到Property的属性中
column:指定分布查询中以什么条件进行查询
@Test
public void testGetEmpAndDeptByStep(){SqlSession sqlSession= SQLSessionUtil.getSQLSession();EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);Emp emp = mapper.getEmpAndDeptByStep(1);System.out.println(emp.toString());sqlSession.close();
}
结果:
DEBUG 08-09 15:30:10,110 ==> Preparing: select emp_id,emp_name,age,gender,t_emp.dept_id,dept_name from t_emp left join t_dept on t_emp.dept_id=t_dept.dept_id where emp_id = ?;(BaseJdbcLogger.java:137)
DEBUG 08-09 15:30:10,150 ==> Parameters: 1(Integer)(BaseJdbcLogger.java:137)
DEBUG 08-09 15:30:10,184 ====> Preparing: select dept_id,dept_name from t_dept where dept_id=?;(BaseJdbcLogger.java:137)
DEBUG 08-09 15:30:10,185 ====> Parameters: 1(Integer)(BaseJdbcLogger.java:137)
DEBUG 08-09 15:30:10,187 <==== Total: 1(BaseJdbcLogger.java:137)
DEBUG 08-09 15:30:10,189 <== Total: 1(BaseJdbcLogger.java:137)
Emp(empId=1, empName=makabkaa, age=18, gender=男, deptId=1, dept=Dept(deptId=1, deptName=A))
可以看到结果中执行了两条语句。
分步查询的好处
分布查询的优势:延迟加载:在其值没有使用的时候不去加载Sql语句,从而节省内存:例如我们将上面的System.out.println(emp.toString());更改为System.out.println(emp.getEmpName());此时,由于没有使用到其Dept属性,所以懒加载不会去执行关于查询Dept对象的Sql,节省了内存开销。
需要在mybatis-config.xml中配置标签
<settings>
<!-- 将下划线映射为驼峰--><setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启延迟加载--><setting name="lazyLoadingEnabled" value="true"/>
<!-- 开启按需加载:设置为false--><setting name="aggressiveLazyLoading" value="false"/>
</settings>
以上两个设置需要将lazyLoadingEnable设置为true,将aggressiveLazyLoading设置为false才是按需加载
lazyLoadingEnabled默认为false。
aggressiveLazyLoading 3.4.1(包含)前为true,之后默认为false。
开启懒加载一定要两个属性都配置吗?
不是的,真正开启懒加载标签的只是lazyLoadingEnabled:true表示开启,false表示关闭,默认为false。
如果我不想将全部的级联都设计懒加载怎么办?
association和collection有个fetchType属性可以覆盖全局的懒加载状态:eager表示这个级联不使用懒加载要立即加载,lazy表示使用懒加载。
aggressiveLazyLoading是做什么的?
它是控制具有懒加载特性的对象的属性的加载情况的。
true表示如果对具有懒加载特性的对象的任意调用会导致这个对象的完整加载,
false表示每种属性按照需要加载。
注意:discriminator没有fetchType属性
https://blog.csdn.net/qq_42650817/article/details/103262158
我的某个级联查询不想懒加载该怎么办?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VKwaNZOU-1690788149207)(Mybatis.assets/image-20220809155624544.png)]
在开启了懒加载的全局环境中通过fetchType属性来指定是否使用延迟加载,eager为立即执行,lazy为懒执行。
15.3、处理一对多级联查询Collection
第一种方式:Collection + ofType
更改Dept实体类中的内容
package com.lobo.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;import java.util.List;/*** className: Dept<br/>* author: MacieSerenity <br/>* date: 2022-08-09 10:39**/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Dept {/*** 部门ID*/private int deptId;/*** 部门名称*/private String deptName;/*** 员工列表*/private List<Emp> emps;
}
association中的JavaType表示属性的类型
collection中的ofType指的是集合中的类型
/*** 根据DeptID查询所有的员工信息* @param deptId 部门ID* @return Dept对象,其中有List< Emp >属性*/Dept getAllEmpByDeptId(@Param("deptId")int deptId);
<!-- Dept getAllEmpByDeptId(@Param("deptId")int deptId);-->
<select id="getAllEmpByDeptId" resultMap="deptAndEmpResultMap">select t_dept.dept_id,t_dept.dept_name,t_emp.emp_id,t_emp.emp_name,t_emp.age,t_emp.genderfrom t_dept left join t_emp on t_dept.dept_id=t_emp.dept_idwhere t_dept.dept_id=#{deptId};
</select>
注意resultMap中的collection属性
<resultMap id="deptAndEmpResultMap" type="Dept"><id column="dept_id" property="deptId" /><result column="dept_name" property="deptName"/><collection property="emps" ofType="Emp"><id column="emp_id" property="empId" /><result column="emp_name" property="empName" /><result column="age" property="age" /><result column="gender" property="gender" /></collection>
</resultMap>
@Testpublic void getDeptAndEmps(){SqlSession sqlSession= SQLSessionUtil.getSQLSession();DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);Dept allEmpByDeptId = mapper.getAllEmpByDeptId(1);System.out.println(allEmpByDeptId.toString());sqlSession.close();}
DEBUG 08-09 16:14:38,272 ==> Preparing: select t_dept.dept_id,t_dept.dept_name, t_emp.emp_id,t_emp.emp_name,t_emp.age,t_emp.gender from t_dept left join t_emp on t_dept.dept_id=t_emp.dept_id where t_dept.dept_id=?;(BaseJdbcLogger.java:137)
DEBUG 08-09 16:14:38,305 ==> Parameters: 1(Integer)(BaseJdbcLogger.java:137)
DEBUG 08-09 16:14:38,335 <== Total: 3(BaseJdbcLogger.java:137)
Dept(deptId=1, deptName=A, emps=[Emp(empId=1, empName=makabkaa, age=18, gender=男, deptId=0, dept=null), Emp(empId=3, empName=tom, age=19, gender=男, deptId=0, dept=null), Emp(empId=4, empName=jack, age=20, gender=男, deptId=0, dept=null)])
第二种方式:分步查询的方式
首先我们先创建第一步:
DeptMapper部分
/*** 根据DeptID查询所有的员工信息,但是使用分步查询* @param deptId 部门ID* @return 部门对象*/Dept getALlEmpByDeptIdStep(@Param("deptId")int deptId);
<select id="getALlEmpByDeptIdStep" resultMap="getAllEmpByDeptIdStepMap">select t_dept.dept_id,t_dept.dept_namefrom t_dept where dept_id=#{deptId}
</select>
注意resultMap的写法
<resultMap id="getAllEmpByDeptIdStepMap" type="Dept"><id column="dept_id" property="deptId"/><result column="dept_name" property="deptName" /><collection property="emps"select="com.lobo.mapper.EmpMapper.getDeptAndEmpByStepTwo"column="dept_id"></collection>
</resultMap>
第一步我们指定了Collection的值对应我们DEPT中的emps这个属性,然后指定搜索条件为dept_id
然后我们需要指定select,也就是根据以上这个条件进行搜索的语句如下:
EmpMapper部分
/*** 分步查询第二步,根据deptid查询Emp* @param deptId 部门名称* @return emp对象的集合*/List<Emp> getDeptAndEmpByStepTwo(@Param("deptId")int deptId);
<!-- List<Emp> getDeptAndEmpByStepTwo(@Param("deptId")int deptId);-->
<select id="getDeptAndEmpByStepTwo" resultType="Emp">select emp_id,emp_name,age,gender,dept_idfrom t_empwhere dept_id = #{deptId};
</select>
然后我们确定一下 select=“com.lobo.mapper.EmpMapper.getDeptAndEmpByStepTwo” 也就是指定当前我们写的这个方法来查询
<collection property="emps"select="com.lobo.mapper.EmpMapper.getDeptAndEmpByStepTwo"column="dept_id">
测试:
@Test
public void getDeptAndEmpByStep(){SqlSession sqlSession= SQLSessionUtil.getSQLSession();DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);Dept allEmpByDeptId = mapper.getALlEmpByDeptIdStep(1);System.out.println(allEmpByDeptId.toString());sqlSession.close();
}
结果:可以看到执行了两次SQL语句
DEBUG 08-09 16:44:01,007 ==> Preparing: select t_dept.dept_id,t_dept.dept_name from t_dept where dept_id=?(BaseJdbcLogger.java:137)
DEBUG 08-09 16:44:01,049 ==> Parameters: 1(Integer)(BaseJdbcLogger.java:137)
DEBUG 08-09 16:44:01,129 <== Total: 1(BaseJdbcLogger.java:137)
DEBUG 08-09 16:44:01,132 ==> Preparing: select emp_id,emp_name,age,gender,dept_id from t_emp where dept_id = ?;(BaseJdbcLogger.java:137)
DEBUG 08-09 16:44:01,133 ==> Parameters: 1(Integer)(BaseJdbcLogger.java:137)
DEBUG 08-09 16:44:01,137 <== Total: 3(BaseJdbcLogger.java:137)
Dept(deptId=1, deptName=A, emps=[Emp(empId=1, empName=makabkaa, age=18, gender=男, deptId=1, dept=null), Emp(empId=3, empName=tom, age=19, gender=男, deptId=1, dept=null), Emp(empId=4, empName=jack, age=20, gender=男, deptId=1, dept=null)])
16、动态SQL
动态SQL是一种根据特定条件动态拼装SQL语句的功能,存在的意义是为了解决拼接SQL语句字符串时的问题。
16.1、IF 多条件查询
if用于判断某个条件,根据条件的正确与否,动态的添加SQL语句到SQL之后
我们使用上个案例的EMP实体表,然后创建一个DynamicSQLMapper
/*** 根据条件查询员工信息* @param emp 员工对象* @return 一条或者多条记录*/List<Emp> getEmpByCondition(Emp emp); //因为不知道会返回多少条数据,所以使用List进行封装
<select id="getEmpByCondition" resultType="Emp">select emp_id,emp_name,age,gender,dept_id from t_emp where<if test="empId != null and empId != ''">emp_id = #{empId}</if><if test="empName != null and empName != ''">and emp_name = #{empName}</if><if test="age != null and age != ''">and age = #{age}</if><if test="gender != null and gender != ''">and gender = #{gender}</if>
</select>
在测试时,我们使用if进行判断,然后判断的结果为true时,就会将下面的SQL语句进行拼接
我们传入一个Emp对象(且所有属性都有值的情况下)
这样的写法有以下问题:
当EmpID为空时,动态的语句就会变成 select emp_id,emp_name,age,gender,dept_id from t_emp where and…
此时就会报错说SQL语句错误,且当所有属性都没有值时,SQL语句会变成select emp_id,emp_name,age,gender,dept_id from t_emp where
又多出了一个where语句,这样我们的SQL就没办法执行,且将所有的EMP属性都填上也不符合需求
因此我们要对以上的语句进行改进,使得属性之间无论是否为空,都不会冲突
第一种方法-恒成立公式
<select id="getEmpByCondition" resultType="Emp">select emp_id,emp_name,age,gender,dept_id from t_emp where 1=1<if test="empId != null and empId != ''">and emp_id = #{empId}</if><if test="empName != null and empName != ''">and emp_name = #{empName}</if><if test="age != null and age != ''">and age = #{age}</if><if test="gender != null and gender != ''">and gender = #{gender}</if>
</select>
我们在where 后面加了一个 1=1 的恒成立的等式,这个运算百分百为真且不影响SQL语句的查询结果,在后面属性进行连接时,也不会出现冲突的问题
语句会变成select emp_id,emp_name,age,gender,dept_id from t_emp where 1=1 and …
SQL语句便没有了冲突,可以正常使用。
第二种方法-where标签
使用where标签,动态生成SQL语句
where标签的两个作用:
1、自动生成where关键字
2、若有单个标签成立,只会将条件前面的and去掉,但是内容后多余的and无法去掉
3、当where标签包裹的内容中没有任何条件成立或者生成时,就会自动去除where标签,不会有任何功能
<select id="getEmpByCondition" resultType="Emp">select emp_id,emp_name,age,gender,dept_id from t_emp<where><if test="empId != null and empId != ''">and emp_id = #{empId}</if><if test="empName != null and empName != ''">and emp_name = #{empName}</if><if test="age != null and age != ''">and age = #{age}</if><if test="gender != null and gender != ''">and gender = #{gender}</if></where>
</select>
测试类:
@Test
public void testGetEmpByCondition(){SqlSession sqlSession= SqlsessionUtil.getSqlSession();DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);Emp emp = new Emp();emp.setEmpName("tom");List<Emp> empByCondition = mapper.getEmpByCondition(emp);empByCondition.forEach(System.out::println);
}
结果:
DEBUG 08-09 18:00:09,595 ==> Preparing: select emp_id,emp_name,age,gender,dept_id from t_emp WHERE emp_name = ?(BaseJdbcLogger.java:137)
DEBUG 08-09 18:00:09,634 ==> Parameters: tom(String)(BaseJdbcLogger.java:137)
DEBUG 08-09 18:00:09,671 <== Total: 1(BaseJdbcLogger.java:137)
Emp(empId=3, empName=tom, age=19, gender=男, deptId=1)
Disconnected from the target VM, address: '127.0.0.1:5427', transport: 'socket'Process finished with exit code 0
第三种方式-trim
trim
prefix、suffix:在标签中内容前面或者后面添加指定内容
prefixOverriedes,suffixoverrides:在标签中内容前面或者后面去掉指定内容
Where方式:
<select id="getEmpByCondition" resultType="Emp">select emp_id,emp_name,age,gender,dept_id from t_emp<where><if test="empId != null and empId != ''">and emp_id = #{empId}</if><if test="empName != null and empName != ''">and emp_name = #{empName}</if><if test="age != null and age != ''">and age = #{age}</if><if test="gender != null and gender != ''">and gender = #{gender}</if></where>
</select>
Trim方式:
<select id="getEmpByConditionTrim" resultType="Emp">select emp_id,emp_name,age,gender,dept_id from t_emp<trim prefix="where" prefixOverrides="and"><if test="empId != null and empId != ''">and emp_id = #{empId}</if><if test="empName != null and empName != ''">and emp_name = #{empName}</if><if test="age != null and age != ''">and age = #{age}</if><if test="gender != null and gender != ''">and gender = #{gender}</if></trim>
</select>
16.2、Choose、when、otherwise
相当于Java中的 if....else if .... else
when至少设置一个,otherwise最多设置一个
/*** 使用Choose语句实现动态SQL* @param emp 用户信息* @return 匹配的用户*/List<Emp> getEmpByChoose(Emp emp);
<select id="getEmpByChoose" resultType="Emp">select emp_id,emp_name,age,gender,dept_id from t_emp<where><choose><when test="empId!=null and empId!=''">emp_id = #{empId}</when><when test="empName!=null and empName!=''">emp_name = #{empName}</when><when test="age!=null and age!=''">age = #{age}</when><when test="gender!=null and gender!=''">gender = #{gender}</when><when test="deptId!=null and deptId!=''">dept_id = #{deptId}</when></choose></where></select>
@Testpublic void testGetEmpByChoose(){SqlSession sqlSession= SqlsessionUtil.getSqlSession();DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);Emp emp = new Emp();emp.setEmpName("tom");
// emp.setAge(19);List<Emp> empByCondition = mapper.getEmpByChoose(emp);empByCondition.forEach(System.out::println);}
DEBUG 08-10 09:47:28,446 ==> Preparing: select emp_id,emp_name,age,gender,dept_id from t_emp WHERE emp_name = ?(BaseJdbcLogger.java:137)
DEBUG 08-10 09:47:28,483 ==> Parameters: tom(String)(BaseJdbcLogger.java:137)
DEBUG 08-10 09:47:28,598 <== Total: 1(BaseJdbcLogger.java:137)
Emp(empId=3, empName=tom, age=19, gender=男, deptId=1)
16.3、foreach
collection 需要循环的数组或集合
item 用一个字符表示数组或集合中的一个数据
separator 设置每次循环之间的分隔符
open 整个循环开始之前的符号
close 整个循环结束之后的符号
批量添加(以集合的形式遍历)
/*** 批量添加空部门的员工* @param emps 员工对象的列表* @return 添加的行数*/int insertMultipleEmp(@Param("emps") List<Emp> emps);
<!-- int insertMultipleEmp(@Param("emps") List<Emp> emps);--><insert id="insertMultipleEmp">insert into t_emp(emp_name,age,gender,dept_id) values<foreach collection="emps" item="emp" separator=",">(#{emp.empName},#{emp.age},#{emp.gender},0)</foreach></insert>
注意这里 foreach 的位置位于values后面,也就是需要循环的位置的开始
collection是指传过来的集合的名称,建议使用@Param注解进行声明,然后item指定集合内单个单位的名称,然后指定separator为分隔符,然后括号内按正常的values(),(),()便可以完成多次添加。
@Testpublic void testInsertMultipleEmp(){SqlSession sqlSession= SqlsessionUtil.getSqlSession();DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);Emp emp = new Emp("tommm",20,"男");Emp emp2 = new Emp("marji",20,"女");Emp emp3 = new Emp("jms",20,"男");List<Emp> emps = Arrays.asList(emp, emp2, emp3);int result = mapper.insertMultipleEmp(emps);System.out.println(result>1?"插入成功":"插入数据失败");sqlSession.close();}
结果:
DEBUG 08-10 10:20:52,100 ==> Preparing: insert into t_emp(emp_name,age,gender,dept_id) values (?,?,?,0) , (?,?,?,0) , (?,?,?,0)(BaseJdbcLogger.java:137)
DEBUG 08-10 10:20:52,140 ==> Parameters: tommm(String), 20(Integer), 男(String), marji(String), 20(Integer), 女(String), jms(String), 20(Integer), 男(String)(BaseJdbcLogger.java:137)
DEBUG 08-10 10:20:52,147 <== Updates: 3(BaseJdbcLogger.java:137)
插入成功
mysql> select * from t_emp;
+--------+----------+------+--------+---------+
| emp_id | emp_name | age | gender | dept_id |
+--------+----------+------+--------+---------+
| 1 | makabkaa | 18 | 男 | 1 |
| 2 | wuxidixi | 18 | 女 | 2 |
| 3 | tom | 19 | 男 | 1 |
| 4 | jack | 20 | 男 | 1 |
| 5 | lucy | 21 | 男 | 2 |
| 6 | tommm | 20 | 男 | 0 | <==
| 7 | marji | 20 | 女 | 0 | <==
| 8 | jms | 20 | 男 | 0 | <==
+--------+----------+------+--------+---------+
8 rows in set (0.00 sec)
批量删除(数组方式)
/*** 通过Integer数组里面存的多个ID完成批量删除* @param empIds Integer数组* @return 影响的行数*/int deleteMultipleEmpByIds(@Param("empIds")Integer[] empIds);
<!-- int deleteMultipleEmpByIds(@Param("empIds")Integer[] empIds);-->
<delete id="deleteMultipleEmpByIds">delete from t_emp where emp_id in(<foreach collection="empIds" item="empId" separator=",">#{empId}</foreach>)
</delete>
注意不要忘了foreach标签()两边的括号
使用open和close两个属性指定开始和结束(指的是循环开始前和循环开始后,不是每个循环都会打印)
<delete id="deleteMultipleEmpByIds">delete from t_emp where emp_id in<foreach collection="empIds" item="empId" separator="," open="(" close=")">#{empId}</foreach>
</delete>
or的写法
delete from t_emp where
<foreach collection="empIds" item="empId" separator="or">emp_id = #{empId}
</foreach>生成的SQL:delete from t_emp where emp_id in delete from t_emp where emp_id = ? or emp_id = ? or emp_id = ?
@Test
public void testDeleteMultipleEmp(){SqlSession sqlSession= SqlsessionUtil.getSqlSession();DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);Integer deleteIds[]={6,7,8};int result = mapper.deleteMultipleEmpByIds(deleteIds);System.out.println(result>1?"删除数据成功":"删除数据失败");sqlSession.close();
}
DEBUG 08-10 10:34:38,582 ==> Preparing: delete from t_emp where emp_id in ( ? , ? , ? )(BaseJdbcLogger.java:137)
DEBUG 08-10 10:34:38,617 ==> Parameters: 6(Integer), 7(Integer), 8(Integer)(BaseJdbcLogger.java:137)
DEBUG 08-10 10:34:38,622 <== Updates: 3(BaseJdbcLogger.java:137)
删除数据成功
16.4、SQL片段
<sql id="empBaseColumn">emp_id,emp_name,age,gender,dept_id
</sql>
如何使用:使用include标签,然后将refid指定为sql片段的ID
<select id="getEmpByCondition" resultType="Emp">select <include refid="empBaseColumn"/> from t_emp</select>
17、Mybatis缓存
17.1、一级缓存
Mybatis的一级缓存是默认开启的。
Mybatis的一级缓存是SQLSession级别的,通过同一个SqlSession查询的数据会被缓存,再次使用同一个SqlSession查询同一条数据,会从缓存中获取(同一个sqlSession中查询数据可以共享数据)
一级缓存失效的原因:
不同的SqlSession中查询
同一个SqlSession但是查询语句不同
同一个SqlSession的两次查询中执行了一次增删改操作(改变了数据)
同一个SqlSession两次查询期间手动清空了缓存 sqlSession.clearCache();clearCache只会删除一级缓存
/*** 根据EmpID获取Emp对象* @param empID Emp对象的ID* @return ID相同的EMP对象*/Emp getEmpById(@Param("empId")Integer empID);
<sql id="EmpAllColumn">emp_id,emp_name,age,gender,dept_id</sql>
<!-- Emp getEmpById(@Param("empId")Integer empID);--><select id="getEmpById" resultType="Emp">select <include refid="EmpAllColumn" /> from t_emp where emp_id = #{empId};</select>
@Test
public void testGetEmpById(){//同一个SQLSession中SqlSession sqlSession = SqlSessionUtil.getSQLSession();CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);Emp empById = mapper.getEmpById(1);System.out.println(empById.toString());Emp empById2 = mapper.getEmpById(1);System.out.println(empById2.toString());sqlSession.close();//不同的SqlSession中CacheMapper mapper2 = sqlSession.getMapper(CacheMapper.class);Emp empById3 = mapper2.getEmpById(1);System.out.println(empById3.toString());
}
我自己运行的时候有误差,可能是默认开启了二级缓存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XNf8Z5Wi-1690788149211)(Mybatis.assets/image-20220810133235507.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YDgGslpB-1690788149216)(Mybatis.assets/image-20220810133113803.png)]
17.2、二级缓存
一级缓存是SqlSession级别的,也就是说,同一个SqlSession查询共用一个缓存
而二级缓存是SqlSessionFactory等级的,也就是说同一个SqlSessionFactory创建的SqlSession中查询的数据会被缓存。
如何开启二级缓存:
在核心配置文件中,设置全局配置cacheEnabled=true,默认为true,所以不需要设置。在映射文件中设置标签<cache/>二级缓存必须在SqlSession关闭或者提交之后有效 (SqlSession.close)查询的数据所转换的实体类类型必须实现序列化的接口
失效的情况:
两次查询之间执行了人因的增删改,会使一级缓存和二级缓存同时失效。
DEBUG 08-10 13:52:12,898 ==> Preparing: select emp_id,emp_name,age,gender,dept_id from t_emp where emp_id = ?;(BaseJdbcLogger.java:137)
DEBUG 08-10 13:52:12,934 ==> Parameters: 1(Integer)(BaseJdbcLogger.java:137)
DEBUG 08-10 13:52:12,960 <== Total: 1(BaseJdbcLogger.java:137)
Emp(empId=1, empName=makabkaa, age=18, gender=男, deptId=1)
Emp(empId=1, empName=makabkaa, age=18, gender=男, deptId=1)
相关配置
eviction:缓存回收策略LRU 默认,最近最少使用 最长时间不被使用的对象FIFO 先进先出 进入的先后顺序移除SOFT 软引用 移除基于垃圾回收期状态和软引用的对象WEAK 弱引用 更积极的移除基于垃圾收集器状态和弱引用规则的对象flushinterval:刷新间隔,毫秒默认不设置,没有刷新间隔,缓存仅仅调用语句时刷新size:引用数目,正整数代表最多可以存储多少个对象,太大会导致溢出readOnly属性:只读 true/falsetrue 给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,有性能优势false 读写缓存,返回对象的拷贝(通过序列化)。会慢一些但是安全,所以默认是false。
17.3、查询的顺序
先查询二级缓存,因为二级缓存中可能会有其他程序已经查询出来的数据,可以直接使用。
先查询二级缓存,若没有查询到,再查询一级缓存,若一级缓存也没有,则查询数据库。
SqlSession关闭之后,一级缓存中的数据才会保存到二级缓存中。
18、Mybatis逆向工程
注意:如果pojo中产生了多个java.1文件,要在pom文件中添加<configuration><overwrite>true</overwrite></configuration>标签!!
我们需要在Pom文件的properties标签下方添加一个build标签,然后具体内容如下
<build><plugins><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.0</version><dependencies>
<!-- mybatis-generator的核心jar包--><dependency><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-core</artifactId><version>1.3.2</version></dependency>
<!-- mySql驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.18</version></dependency></dependencies></plugin></plugins></build>
xml官方文档http://mybatis.org/generator/configreference/xmlconfig.html
具体配置信息解释:https://blog.csdn.net/qq_33326449/article/details/105930655
我们需要阅读上面的使用说明,然后配置好我们的generatorConfig.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfigurationPUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN""http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration><!-- targetRuntime:执行生成的逆向工程的版本--><!-- MybatisSimple:生成基本的CRUD--><!-- Mybatis3:生成带条件的CRUD--><!-- <properties resource="./jdbc-config.properties"/>--><context id="DB2Tables" targetRuntime="MyBatis3"><!-- 数据库连接信息--><!-- <jdbcConnection driverClass="${jdbc.driver}"--><!-- connectionURL="${jdbc.url}"--><!-- userId="${jdbc.username}"--><!-- password="${jdbc.password}"> --><jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"connectionURL="jdbc:mysql://localhost:3306/mybatisstudy?serverTimezone=GMT"userId="root"password="li1473606768"></jdbcConnection><!-- JavaBean的生成策略--><javaModelGenerator targetPackage="com.lobo.entity"targetProject=".\src\main\java"><property name="enableSubPackages" value="true"/><property name="trimStrings" value="true"/></javaModelGenerator><!--SQL映射文件的生成策略--><sqlMapGenerator targetPackage="com.lobo.mapper"targetProject=".\src\main\resources"><property name="enableSubPackages" value="true"/></sqlMapGenerator><!--Mapper接口的生成策略--><javaClientGenerator type="XMLMAPPER" targetPackage="com.lobo.mapper" targetProject=".\src\main\java"><property name="enableSubPackages" value="true"/></javaClientGenerator><!--逆向分析表--><!-- tableName设置为*号,可以对应所有表,不写domainObject--><!-- domainObjectName指定生成出来的实体类的类名--><table tableName="t_emp" domainObjectName="Emp"/><table tableName="t_dept" domainObjectName="Dept"/></context>
</generatorConfiguration>
然后点击右边的maven->plugin->mybatis-generator
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wjPbWba7-1690788149221)(Mybatis.assets/image-20220810173607858.png)]
然后插件就会自动生成以下文件:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HCu9XaZe-1690788149226)(Mybatis.assets/image-20220810173547052.png)]
使用方法:
测试类:
@Test
public void test() {SqlSession sqlSession = SqlsessionUtil.getSqlSession();EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);// 条件类EmpExample example=new EmpExample();// 添加条件桩example.createCriteria().andDeptIdEqualTo(1);// 排序example.setOrderByClause("age");// 执行List<Emp> emp = mapper.selectByExample(example);System.out.println(emp);sqlSession.close();
}
注意:Mybatis会生成一个EmpMapper类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eLUeX4hT-1690788149230)(Mybatis.assets/image-20220810173831408.png)]
要注意这些方法之间的区别,如insert和insertSelective,insert方法会将你传输的数据中的所有数据,包括null的数据进行插入,也就是说如果你的数据中有一项为null时,insert也会进行插入;
而insertSelective方法会忽略掉所有null的属性,只插入其它不为空的项目;
要注意不同方法之间的区别,还有例如update\updateSelective。
19、mybatis配置分页插件PageHelper
PageHelper官网:
https://pagehelper.github.io/docs/howtouse/
limit index,pageSize
pageSize=10
pageNum=n
pageSize=4,pageNum=1 ==>limit 0,4
pageSize=4,pageNum=3 ==>limit 8,4
pageSize=4,pageNum=6 ==>limit 20,4总结:totalSize:总记录数
totalPage=totalSize/pageSize
if(totalSize % pageSize !=0) totalPage++;index=pageSize*(pageNum-1)
加入依赖
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.2.0</version>
</dependency>
在mybatis-config.xml中配置:
Interceptor:拦截器
<!-- pageHelper--><plugins><plugin interceptor="com.github.pagehelper.PageInterceptor" /></plugins>
注意plugin 的位置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y0aHklHG-1690788149235)(Mybatis.assets/image-20220810184749173.png)]
测试类
@Testpublic void testPage(){SqlSession sqlSession = SqlsessionUtil.getSqlSession();EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);//查询功能之前开启分页 返回和分页相关的数据Page<Object>//Page<Object>继承了ArrayList,本质上是一个数组Page<Object> page = PageHelper.startPage(1, 4);List<Emp> emp = mapper.selectByExample(null);//这里返回的数据条数就已经只有4条了。//PageInfo中的关于分页的信息比Page<Object>中的数据更多PageInfo<Emp> pageInfo=new PageInfo<>(emp,2);System.out.println("查询出来的数据");emp.forEach(System.out::println);System.out.println("查询出来的page对象");System.out.println(page);System.out.println("查询出来的pageInfo对象");System.out.println(pageInfo);}
查询出来的数据
Emp(empId=1, empName=makabkaa, age=18, gender=男, deptId=1)
Emp(empId=2, empName=wuxidixi, age=18, gender=女, deptId=2)
Emp(empId=3, empName=tom, age=19, gender=男, deptId=1)
Emp(empId=4, empName=jack, age=20, gender=男, deptId=1)查询出来的page对象
Page{count=true, pageNum=1, pageSize=4, startRow=0, endRow=4, total=5, pages=2, reasonable=false, pageSizeZero=false}[Emp(empId=1, empName=makabkaa, age=18, gender=男, deptId=1), Emp(empId=2, empName=wuxidixi, age=18, gender=女, deptId=2), Emp(empId=3, empName=tom, age=19, gender=男, deptId=1), Emp(empId=4, empName=jack, age=20, gender=男, deptId=1)]查询出来的pageInfo对象
PageInfo{pageNum=1, pageSize=4, size=4, startRow=1, endRow=4, total=5, pages=2, list=Page{count=true, pageNum=1, pageSize=4, startRow=0, endRow=4, total=5, pages=2, reasonable=false, pageSizeZero=false}[Emp(empId=1, empName=makabkaa, age=18, gender=男, deptId=1), Emp(empId=2, empName=wuxidixi, age=18, gender=女, deptId=2), Emp(empId=3, empName=tom, age=19, gender=男, deptId=1), Emp(empId=4, empName=jack, age=20, gender=男, deptId=1)], prePage=0, nextPage=2, isFirstPage=true, isLastPage=false, hasPreviousPage=false, hasNextPage=true, navigatePages=2, navigateFirstPage=1, navigateLastPage=2, navigatepageNums=[1, 2]}
pageInfo中的属性的信息:
pageNum:当前的页码
pageSize:每页显示的条数
size:当前页显示的真实的条数
total:总记录数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页的页码数
navigateNums:导航分页的页码 如 [1,2,3]