1.Mybatis简介
1.1MyBatis特性
MyBatis是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架
MyBatsi避免了几乎所有的JDBC代码和手动设置参数以及获取结果集
MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录
MyBatis是一个半自动的ORM(Object Relation Mapping)框架
1.2持久层技术对比
1.2.1JDBC
SQL夹杂在Java代码中耦合度高,导致硬编码内伤
维护不易且实际开发需求中SQL有变化,频繁修改的情况多见
代码冗长,开发效率低
1.2.2Hibernate
操作简单,开发效率高
程序中的长难复杂SQL需要绕过框架
内部自动生产的SQL,不容易做特殊优化
基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难
反射操作太多,导致数据库性能下降
1.2.3MyBatis
轻量级,性能出色
SQL和Java编码分开,功能边界清晰,Java代码专注业务、SQL语句专注数据
开发效率稍逊于Hibernate,但是完全能够接受
2.MyBatis框架搭建及简单测试
ORM:Object Relationship Mapping,对象关系映射。
- 对象:Java的实体类的对象
- 关系:关系型数据库
- 映射:二者之间的对应关系
Java概念 | 数据库概念 |
---|---|
类 | 表 |
属性 | 字段/列 |
对象 | 2.1MyBatis配置文件记录/行 |
2.1MyBatis配置文件
resources下新建mybatis-config.xml文件,写入配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--配置连接数据库的环境-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/XXX"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<mappers>
<mapper resource="mapper/XXXMapper.xml"
</mappers>
</configuration>
2.2MyBatis映射文件创建
2.2.1映射文件的命名规则
表所对应的实体类的类名+Mapper.xml,如:UserMapper.xml
因此一个映射文件对应一个实体类,对应一张表的操作
MyBatis映射文件用于编写SQL,访问以及操作表中的数据
MyBatis映射文件存放的位置是src/main/resources/mappers目录下
2.2.2MyBatis面向接口编程的一致性
【表----实体类----mapper接口----映射文件】
MyBatis面向接口编程的两个一致:
- 映射文件的namespace要和mapper接口的全类名保持一致
- 映射文件中SQL语句的id要和mapper接口中的方法名一致
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="com.....mapper.XXXMapper">
Mapper接口中:
public interface UserMapper{
//添加用户信息
int insertUser();
}
Mapper.xml中:
<insert id="insertUser">
insert into t_user values(null,'admin','123456',23,'男','123456@qq.com')
</insert>
2.3MyBatis简单测试-添加功能
通过sqlSession.getMapper(XXXMapepr.class)方法来获取mapper接口
调用mapper接口执行完SQL后需要手动提交事务
@Test
public void test() throws IOException{
//加载核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取mapper接口对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//测试功能
int result = mapper.insertUser();
//手动提交事务,不提交事务的话没效果
sqlSession.commit();
sout("result: "+result);
}
2.4功能优化
2.4.1自动提交事务
上述实现,在每一次SQL执行后都需要手动提交事务,sqlSession默认不自动提交事务
若需要自动提交事务,可以使用sqlSessionFactory.openSession(true)方法,把参数设置成true,默认是false,true表示自动提交
@Test
public void test() throws IOException{
//加载核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactoryBuilder
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//获取SqlSession,参数设置为true为自动提交,默认是false
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//获取mapper接口对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//测试功能
int result = mapper.insertUser();
sout("result: "+result);
}
2.4.2加入log4j日志功能
在SQL执行过程中希望在控制台打印出执行过程,可加入log4j日志功能
1.加入依赖:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2.加入log4j的配置文件:
log4j的配置文件名为log4j.xml,存放的位置是src/main/resources目录下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<!-- 将日志信息输出到控制台 -->
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encodeing" value="UTF-8" />
<!-- 设置日志输出的样式 -->
<layout class="org.apache.log4j.PatternLayout">
<!-- 设置日志输出的格式 -->
<param name="ConversionPattern" value="%-5p %d{yyyy-MM-dd HH:mm:ss:SSS} %m(%F:%L) \n" />
</layout>
<!--过滤器设置输出的级别-->
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<!-- 设置日志输出的最小级别 -->
<param name="levelMin" value="WARN" />
<!-- 设置日志输出的最大级别 -->
<param name="levelMax" value="ERROR" />
<!-- 设置日志输出的xxx,默认是false -->
<param name="AcceptOnMatch" value="true" />
</filter>
</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(警告)>INFO(信息)>DEBUG(调试)
从左到右打印的内容越来越详细
2.5MyBatis其他功能测试
2.5.1修改功能
从接口开始写,先在mapper接口里面定义方法,再写.xml中的实现SQL
void updateUser();
<update id="updateUser">
update t_user set username = "张三" where id = 4
</update>
@Test
public void testUpdate() throws IOException{
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateUser();//无返回值
}
2.5.2删除功能
void delUser();
<update id="delUser">
delete from t_user where id = 4
</update>
@Test
public void testDel() throws IOException{
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.delUser();//无返回值
}
2.5.3查询功能
查询是有返回值的,在写之前要设置好
注意:
- 查询功能的标签必须设置resultType或resultMap,不然无法找到返回值类型进行映射
- resultType:设置默认的映射关系
- resultMap:设置自定义的映射关系
2.5.3.1查询单个信息
//根据id返回查询用户信息
User getUserById();
<select id="getUserById" resultType="com.....pojo.User">
select * from t_user where id = 3
</select>
@Test
public void test() throws IOException{
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//测试,返回一个User对象
User user = mapper.getUserById;
sout(user);
}
2.5.3.2查询集合信息
//查询所有的用户信息
List<User> getAllUser();
<select id="getAllUser" resultType="com....pojo.User" >
select * from t_user
</select>
@Test
public void test() throws IOException{
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//测试,返回一个User对象
List<User> userList = mapper.getAllUser;
userList.forEach(user -> sout(user));
}
3.核心配置文件
3.1environment详解
-
environments:配置多个连接数据库的环境
属性:
default:设置默认使用的环境id -
environment:配置某个连接数据库的具体环境
属性:
idL:表示连接数据库环境的唯一标识,不能重复 -
transactionManager:设置事务管理方式
属性:
type=”JDBC|MANAGED“
JDBC:表示当前环境中,执行SQL时,使用的是JDBC中原生的事务管理方式,事务的提交或回滚需要手动处理
MANAGED:被管理,例如Spring
-
dataSource:配置数据源
属性:
type:设置数据源的类型
type=”POOLED|UNPOOLED|JNDI“
POOLED:表示使用数据库连接池缓存数据库连接
UNPOOLED:表示不使用数据库连接池
JNDI:表示使用上下文中的数据源
<!--配置多个连接数据库的环境-->
<environments default="development">
<!--配置某个连接数据库的具体环境-->
<environment id="development">
<!--设置事务管理方式-->
<transactionManager type="JDBC"></transactionManager>
<!--配置数据源-->
<dataSource type="POOLED">
<!--设置连接数据库的驱动-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<!--设置连接数据库的地址-->
<property name="url" value="jdbc:mysql://localhost:3306/XXX"/>
<!--设置连接数据库的用户名-->
<property name="username" value="root"/>
<!--设置连接数据库的密码-->
<property name="password" value="123456"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/XXX"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
3.2properties详解
可以创建jdbc.properties文件来单独存放数据库配置信息
jdbc.properties文件中:
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/XXX
jdbc.username = root
jdbc.password = 123456
mybatis-config.xml文件中:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--引入properties文件,绑定properties文件中的配置-->
<rpoperties resource="jdbc.properties" />
<!--配置连接数据库的环境-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<!--value值可以替换成properties文件中相应的键-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<mappers>
<mapper resource="mapper/XXXMapper.xml"
</mappers>
</configuration>
3.3typeAliases详解
如下,在resultType里面需要每一次都写实体类路径全称,比较麻烦
<select id="getAllUser" resultType="com....pojo.User" >
select * from t_user
</select>
MyBatis提供了类型别名的功能:typeAliases标签,设置默认/指定的类型别名。写了alias属性代表指定用"User"直接代替"com…pojo.user",如果不写则默认使用实体类名代替,且不区分大小写
-
typeAliases:设置类型别名
-
typeAlias:设置某个类型的别名
属性:
type:设置需要设置别名的类型
alias:设置某个类型的别名。如设置,则是指定值;如不设置,则默认使用实体类的类名,不区分大小写
-
package:以包为单位,将包下所有的类型设置默认的类型别名,即类名且不区分大小写
注意:MyBatis核心配置文件中,标签需要按照顺序去配置,顺序如下:
properties?----settings?----typeAliases?----typeHandlers?----objectFactory?----objectWrapperFactory?----reflectorFactory?----plugins?----environments?----databaseIdProvider?----mappers?
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--引入properties文件,绑定properties文件中的配置-->
<properties resource="jdbc.properties" />
<!--设置类型别名-->
<typeAliases>
<!--写了alias属性代表指定用"User"直接代替"com....pojo.user",如果不写则默认使用实体类名代替,且不区分大小写-->
<!--<typeAlias type="com....pojo.user" alias="User"><typeAlias/>-->
<!--以包为单位,将包下所有的类型设置默认的类型别名,即类名且不区分大小写-->
<package name="com....pojo"/>
</typeAliases>
<!--配置连接数据库的环境-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<!--value值可以替换成properties文件中相应的键-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<mappers>
<!--name里面填入.xml文件放置的包名-->
<package name="com.atguigu.mybatis.mapper" />
</mappers>
</configuration>
上述类型别名设置后,.xml中的resultType值可以直接使用实体类名,且不区分大小写
<select id="getAllUser" resultType="User" >
select * from t_user
</select>
3.4mappers详解
上述映射文件的引入,都是单个引入,后续.xml文件增多后不太方便
<!--引入映射文件-->
<mappers>
<mapper resource="mapper/XXXMapper.xml"
</mappers>
MyBatis提供以包为单位进行映射文件的引入
-
package:以包为单位引入映射文件
要求:不然无法绑定识别
- mapper接口所在的包要和映射文件所在的包一致
- mapper接口要和映射文件的名字一致
<mappers>
<!--name里面填入.xml文件放置的包名-->
<package name="com.atguigu.mybatis.mapper"
</mappers>
注意:resources底下创建包时没有package,只能创建directory,创建的时候名字不能直接用“com.atguigu.mybatis.mapper”,这种创建出来的只是一个文件夹,没有目录结构,需要使用“com/atguigu/mybatis/mapper”去创建,这样才是一个有目录层级的包,创建完成后也会转换成“.”的格式
4.IDEA中使用MyBatis
4.1IDEA中模板设置
打开File–Settings–Editor–File and Code Template
然后添加mybatis-config模板,将内容复制到这里面,模板名Name为mybatis-config,后缀Extension为xml
这样的话在新项目中配置mybatis就可以直接创建模板文件,只需要在里面修改内容即可
其他模板创建也类似,如创建mybatis-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="">
</mapper>
4.2封装SqlSessionUtils工具类
获取sqlSession对象的过程是重复的,我们可以把它单独拧出来封装在utils工具类中
创建一个utils包,新建SqlSessionUtils类,在里面写获取SqlSession对象的代码
public class SqlSessionUtils {
public static SqlSession getSqlSession() {
SqlSession sqlSession = null;
try {
//加载核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(true);
} catch(IOException e) {
e.printStackTrace();
}
return sqlSession;
}
}
测试使用:
@Test
public void test(){
//直接调用SqlSessionUtils工具类获取sqlSession对象
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getAllUser;
userList.forEach(user -> sout(user));
}
5.MyBatis获取参数值情况
MyBatis获取参数值的两种方式:${}和#{}
- ${}:本质字符串拼接,要注意单引号‘ ’问题
- #{}:本质占位符赋值,更推荐使用
- 以上两种可根据实际场景选用
MyBatis获取参数值的各种情况:
5.1mapper接口方法的参数为单个
可以通过KaTeX parse error: Expected 'EOF', got '#' at position 4: {}和#̲{}以任意的名称获取参数值,但…{}的单引号问题。具体使用如下:
mapper接口中:
//根据用户名查询用户
User getUserByUsername(String username);
mapper.xml中:
注意:#{username}的值不管填什么都不影响占位符赋值,例如:#{aaa}也可以获取参数值。但是一般推荐使用属性名/字段名,提高代码可阅读性
<select id="getUserByUsername" resultType="User">
select * from t_user where username = #{username}
</select>
使用 :需要加单引号’‘,不然会报错,使用形 式 ′ {}:需要加单引号’ ‘,不然会报错,使用形式' :需要加单引号’‘,不然会报错,使用形式′username}',里面的值跟#{}一样,不受名称影响
<select id="getUserByUsername" resultType="User">
select * from t_user where username = '$username}'
</select>
测试:
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserByUsername(admin);
sout(user);
}
5.2mapper接口方法的参数为多个
此时,MyBatis会将这些参数放在一个map集合中,以两种方式进行存储
- 以arg0,arg1,…为键,以参数为值
- 以param0,param1,…为键,以参数为值
- 以上两种,可以混用,如:arg0,param1方式也可以正常访问
因此,只需要通过#{}和 以键的方式访问值即可,但是需要注意 {}以键的方式访问值即可,但是需要注意 以键的方式访问值即可,但是需要注意{}的单引号问题。具体使用如下:
mapper接口中:
//验证登录
User checkLogin(String username,String password);
mapper.xml中:
<select id="checkLogin" resultType="User">
<!--不能再使用 #{username}方式获取,只能使用MyBatis定义好的arg0/param0方式-->
select * from t_user where username = #{arg0} and password = #{arg1}
</select>
<select id="checkLogin" resultType="User">
<!--不能再使用 '${username}'式获取,只能使用MyBatis定义好的arg0/param0方式-->
select * from t_user where username = '${param0}' and password = '${param1}'
</select>
测试:
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.checkLogin("admin","123456");
sout(user);
}
5.3手动设置map存储多参数
若mapper接口方法的参数有多个时,可以手动将这些参数放在一个map中存储。可以通过KaTeX parse error: Expected 'EOF', got '#' at position 4: {}和#̲{}以任意的名称获取参数值,但…{}的单引号问题。具体使用如下:
mapper接口中:
//验证登录(参数为map)
User checkLoginByMap(Map<String,Object> map);
mapper.xml中:
<select id="checkLoginByMap" resultType="User">
select * from t_user where username = #{username} and password = #{password}
</select>
测试:在map中设置访问的键,则SQL中直接用map的键即可访问
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("username",admin");
map.put("password","123456");
User user = mapper.checkLoginByMap(map);
sout(user);
}
5.4mapper接口方法的参数是实体类
实际情况中,可能从前端传值一个对象进行操作,这时mapper接口方法参数需要是实体类类型
只需要通过KaTeX parse error: Expected 'EOF', got '#' at position 4: {}和#̲{}以属性的方式来访问属性值即…{}的单引号问题。具体使用如下:
mapper接口中:
//添加用户信息
int insertUser(User user);
mapper.xml中:
<insert id="insertUser">
insert into t_user values(null,#{username},#{password},#{age},#{sex},#{email})
</insert>
测试:
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int result = mapper.insertUser(null,"李四","123","21","男","123@qq.com");
sout(result);
}
5.5使用@Param注解命名多参数
可以使用@Param注解来自定义多参数中Map集合访问的键
此时MyBatis会将这些参数放在一个map集合中,以两种方式进行存储
- 以@Param注解的值为键,以参数为值
- 以param0,param1,…为键,以参数为值
因此只需要通过KaTeX parse error: Expected 'EOF', got '#' at position 4: {}和#̲{}以键的方式访问值即可,但是…{}的单引号问题。具体使用如下:
mapper接口中:
//验证登录(使用@param)
User checkLoginByParam(@Param("username") String username, @Param("password") String password)
mapper.xml中:
<select id="checkLoginByMap" resultType="User">
select * from t_user where username = #{username} and password = #{password}
</select>
测试:
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.checkLoginByMap("admin","123456");
sout(user);
}
以上五种情况,除了传输对象(可直接通过属性名访问)外,如果传输的是单个/多个参数值时,建议都使用@Param注解来标识键,比较便捷
6.MyBatis增删改查
6.1MyBatis各种查询
MyBatis的各种查询功能:
-
若查询出的数据只有一条
- 可以通过实体类对象接收
- 可以通过List集合接收【List集合返回值可以为一条数据/null,不会报错】
- 可以通过Map结合接收:以字段为键,字段的值为值
-
若查询出的数据有多条
-
可以通过实体类类型的List集合接收
-
可以通过Map类型的List集合接收
-
可以在mapper接口的方法上添加@MapKey(“”)注解,此时就可以将每条数据转换的map集合作为值,以某个字段的值作为键,放在同一个map集合中
注意:一定不能通过实体类对象接收,此时会抛异常TooManyResultsException
-
6.1.1查询一个/多个实体类对象
mapper中:
//根据id查询用户信息
User getUserById(@Param("id") Integer id);
//查询所有的用户信息
List<User> getAllUser;
mapper.xml中
<select id="getUserById" resultType="User">
select * from t_user where id = #{id}
</select>
<select id="getAllUser" resultType="User">
select * from t_user
</select>
测试类
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(3);
sout(user);
}
@Test
public void testGetAllUser(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getAllUser;
sout(userList);
}
6.1.2查询单行/单列的值
MyBatis中设置了默认的类型别名,且不区分大小写。常用如下:
- java.lang.Integer–>int, integer
- int–>_int, _integer
- Map–>map
- String–>string
mapper中:
//查询用户信息的总记录数
Integer getCount();
mapper.xml中:这里可以直接写Integer/integer/int/Int都行
<select id="getCount" resultType="Integer">
select count(*) from t_user
</select>
测试类
@Test
public void getCount(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int count = mapper.getCount;
sout(count);
}
6.1.3查询返回一个/多个Map集合
使用场景:如果我们查询出来的结果没有任何一个实体类可以与之相对应(如多表查询),则可以用Map集合来接收,Map中就是以字段为键,以字段的值为值
注意:
- 查询多条数据时,每条数据会存放在一个map中,此时有多个map,不能只用一个map接收,要用List<map<>>来接收,不然会跟单个实体类一样的抛异常TooManyResultsException
- 如果不想用List来接收多个map,可以使用@MapKey(“”)注解来指定记录的键,然后记录作为值保存,这样就可以用单个map来接收了,指定的键必须要有唯一性,一般使用id来作为这个键
mapper中:
//根据id查询用户信息为map集合
Map<String,Object> getUserByIdToMap;
//查询所有用户信息为map集合
List<Map<String,Object>> getAllUserToMap;
//查询所有用户信息为map集合
@MapKey("id")
Map<String,Object> getAllUserToMapByMapKey;
mapper.xml中
<select id="getUserByIdToMap" resultType="map">
select * from t_user where id = #{id}
</select>
<select id="getAllUserToMap" resultType="map">
select * from t_user
</select>
<select id="getAllUserToMapByMapKey" resultType="map">
select * from t_user
</select>
测试类
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Object> map = mapper.getUserByIdToMap(3);
sout(map);
}
@Test
public void testGetAllUser(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<Map<String,Object>> mapList = mapper.getAllUser;
sout(mapList);
}
@Test
public void testGetAllUser(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Object> map = mapper.getAllUserToMapByMapKey;
sout(map);
}
6.2特殊SQL执行
6.2.1模糊查询
mapper中:
//根据用户名模糊查询用户信息
List<User> getUserByLike(@Param("username") String username);
mapper.xml中:
- 注意:直接使用’%#{}%'时,#{}不会解析成占位符,会被?代替,被当成字符串的一部分,此时没有占位符,但是我们却要为占位符赋值,便会直接报错,可以使用以下三种方式解决:
<!--1.使用${}解决-->
<select id="getUserByLike" resultType="User">
select * from t_user where username like '%${username}%'
</select>
<!--2.使用concat进行字符串拼接-->
<select id="getUserByLike" resultType="User">
select * from t_user where username like concat('%',#{username},'%')
</select>
<!--3.直接使用双引号""来拼接,推荐使用-->
<select id="getUserByTableName">
select * from ${tableName}
</select>
测试类
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserByLike("a");
sout(userList);
}
6.2.2批量删除
mapper中:
//批量删除
int deleteMore(@Param("ids") String ids);
mapper.xml中
- 注意:在SQL执行过程中,#{}会自动加单引号’ ',但是使用where id in ()时不能加单引号,SQL语句格式不正确,所以只能使用${}来获取参数,而不能用#{}来获取参数
<delete id="deleteMore">
delete from t_user where id in (${ids})
</delete>
测试类
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int result = mapper.deleteMore("1,2,3");
sout(result);
}
6.2.3动态设置表名
mapper中:
//查询指定表中的数据
List<User> getUserByTableName(@Param("tableName") String tableName);
mapper.xml中:
- 注意:在SQL执行过程中,表名是不能加单引号的,所以也不能用#{}来传参,只能用${}
<select id="getUserByTableName">
select * from ${tableName}
</select>
测试类:
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserByTableName("t_user");
sout(userList);
}
6.2.4添加功能获取自增的主键
mapper中:
//添加用户信息
void insertUser();
mapper.xml
- useGeneratedKeys:设置当前标签中的sql使用了自增的主键
- keyProperty:将自增的主键的值赋值给传输搭配映射文件中参数的某个属性
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(null,#{username},#{password},#{age},#{sex},#{email})
</insert>
测试类:
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User(null,"张三","123",23,"男","123.@qq.com")
mapper.insertUser(user);
sout(user);
}
7.MyBatis映射关系
7.1自定义映射resultMap
当数据库字段名和实体类属性值不一致时,例如:数据库中字段使用下划线连接user_name,而实体类属性需要使用驼峰命名userName,此时查询时用userName会显示null,没有匹配的字段,无法映射,需要自定义映射
解决字段名和属性名不一致的情况:
- 为字段起别名,保持和属性名一致
- 设置全局配置,将_自动映射为驼峰:使用时需保证数据库字段符合下划线命名方式,属性也符合驼峰命名。
- 通过resultMap解决字段和属性名的映射关系
7.1.1通过设置字段别名解决字段名和属性名的映射关系
在xml中的SQL语句中指定字段别名user_name userName来完成映射
<select id="getAllUser" resultType="User">
select id,user_name userName,age,sex,email from t_user
</select>
7.1.2通过全局配置mapUnderscoreToCamelCase解决字段名和属性名的映射关系
在mybatis-config.xml中添加settings进行全局配置,来统一将数据库下划线_字段转换为驼峰形式
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--设置MyBatis的全局配置-->
<settings>
<!--将下划线_自动映射为驼峰,user_name:userName-->
<setting name="mapUnderscoreToCamelCase" value=true >
</settings>
</configuration>
7.1.3通过resultMap解决字段和属性名的映射关系
使用resultMap标签可以自定义设置字段与属性间的映射关系
-
id:唯一标识,不能重复,在SQL语句中的resultMap的值便是设置的id的值
-
type:设置映射关系中的实体类类型
-
子标签:
- id:设置主键的映射关系
- result:设置普通字段的映射关系
- 属性:
- property:设置映射关系中的属性名,必须是type属性所设置的实体类类型中的属性名,对应实体类
- column:设置映射关系中的字段名,必须是SQL语句查询出的字段名,对应数据库
mapper.xml中:
<resultMap id="UserResultMap" type="User">
<id property="id" column="id" />
<result property="userName" column="user_name" />
<result property="age" column="age" />
<result property="sex" column="sex" />
<result property="email" column="u_email" />
</resultMap>
<!--返回值类型不用resultType,改为resultMap,值为上述resultMap设置的id值-->
<select id="getAllUser" resultMap="UserResultMap">
select * from t_user
</select>
7.2映射关系:多对一
多对一:对应对象,返回一个对象
处理多对一的映射关系:
- 级联属性赋值
- association标签处理
- 分布查询,使用较多的方式
7.2.1通过级联属性赋值解决多对一的映射关系
mapper中:
//查询员工以及员工所对应的部门信息
Emp getEmpAndDept(@Param("eid") Integer eid);
mapper.xml中:
<resultMap id="empAndDeptResultMap" type="Emp">
<id property="eid" column="eid" />
<result property="empName" column="emp_name" />
<result property="age" column="age" />
<result property="sex" column="sex" />
<result property="email" column="u_email" />
<!--处理多对一映射关系方式一:级联属性赋值-->
<result property="dept.did" column="did" />
<result property="dept.deptName" column="dept_name" />
</resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptResultMap">
select * from t_emp left join t_dept on t_emp.did = t_dept.did
where t_emp.eid = #{eid}
</select>
测试类:
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Emp emp = mapper.getEmpAndDept(1);
sout(emp);
}
7.2.2通过association解决多对一的映射关系
association:专门处理多对一的映射关系
- property:需要处理的属性名
- javaType:该属性的类型
mapper.xml中:
<resultMap id="empAndDeptResultMap" type="Emp">
<id property="eid" column="eid" />
<result property="empName" column="emp_name" />
<result property="age" column="age" />
<result property="sex" column="sex" />
<result property="email" column="u_email" />
<!--处理多对一映射关系方式二:使用association标签来专门处理,javaType填的是另一个实体类的类型-->
<association property="dept" javaType="Dept">
<id property="did" column="did">
<result property="deptName" column="dept_name">
</association>
</resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptResultMap">
select * from t_emp left join t_dept on t_emp.did = t_dept.did
where t_emp.eid = #{eid}
</select>
7.2.3通过分布查询解决多对一的映射关系
mapper中:
//EmpMapper中:
//通过分布查询查询员工以及员工所对应的部门信息
//分布查询第一步:查询员工信息
Emp getEmpAndDeptByStepOne(@Param("eid") Integer eid);
//DeptMapper中:
//通过分布查询查询员工以及员工所对应的部门信息
//分布查询第二步,通过did查询员工所对应的部门
Dept getEmpAndDeptByStepTwo(@Param("did") Integer did)
mapper.xml中:
使用association属性引用分步查询结果
- select:设置分步查询的SQL的唯一标识(namespace.SQLId或mapper接口的全类名.方法名)
- column:设置分布查询的条件
<resultMap id="empAndDeptByStepResultMap" type="Emp">
<id property="eid" column="eid" />
<result property="empName" column="emp_name" />
<result property="age" column="age" />
<result property="sex" column="sex" />
<result property="email" column="u_email" />
<!--处理多对一映射关系方式三:使用association标签的属性引用分部查询结果-->
<association property="dept"
select="com.....DeptMapper.getEmpAndDeptByStepTwo"
column="did">
</association>
</resultMap>
<select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">
select * from t_emp where eid = #{eid}
</select>
<!--DeptMapper.xml中:-->
<select id="getEmpAndDeptByStepTwo" resultType="Dept">
select * from t_dept where did = #{did}
</select>
测试类:
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Emp emp = mapper.getEmpAndDeptByStepOne(1);
sout(emp);
}
7.3延迟加载
分步查询的优点:可以实现延迟加载,但是必须在核心配置文件中设置全局配置信息
lazyLoadingEnable:表示延迟加载是否可用,延迟加载的全局开关。当开启时,所有关联对象都会延迟加载
aggressiveLazyLoading:表示是否按需加载。当开启时,任何方法的调用都会加载该对象的所有属性,不会延迟加载,且设成true后lazyLoadingEnable效果丢失,所以要默认false。否则,每个属性会按需加载
此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql。此时可通过association和collection中的fetchType属性设置当前的分步查询是否使用延迟加载,fetchType=“lazy(延迟加载)|eager(立即加载)”
如果要实现延迟加载:查询员工就只查员工,查询部门就只查部门,不会关联查询员工及对应部门关系。需要将lazyLoadingEnable开启,aggressiveLazyLoading关闭,然后使用fetchType属性来实现按需加载
如下:在mybatis-config.xml中设置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--设置MyBatis的全局配置-->
<settings>
<!--将下划线_自动映射为驼峰,user_name:userName-->
<setting name="mapUnderscoreToCamelCase" value="true" >
<!--开启延迟加载,开启后只会执行当前方法,不会关联查询其他数据,如果不需要延迟加载,则在association中添加fetchType属性来设置-->
<setting name="lazyLoadingEnable" value="true" >
</settings>
</configuration>
mapper.xml中:
使用association属性引用分步查询结果
- select:设置分步查询的SQL的唯一标识(namespace.SQLId或mapper接口的全类名.方法名)
- column:设置分布查询的条件
- fetchType:当开启了全局的延迟加载之后,可通过此属性手动控制延迟加载的效果
- fetchType=“lazy”:表示延迟加载
- fetchType=“eager”:表示立即加载
- 注意:如果不开启全局延迟加载,则fetchType不管填什么值都是立即加载的效果
<resultMap id="empAndDeptByStepResultMap" type="Emp">
<id property="eid" column="eid" />
<result property="empName" column="emp_name" />
<result property="age" column="age" />
<result property="sex" column="sex" />
<result property="email" column="u_email" />
<!--处理多对一映射关系方式三:使用association标签的属性引用分部查询结果-->
<association property="dept"
select="com.....DeptMapper.getEmpAndDeptByStepTwo"
column="did"
fetchType="eager">
</association>
</resultMap>
<select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">
select * from t_emp where eid = #{eid}
</select>
<!--DeptMapper.xml中:-->
<select id="getEmpAndDeptByStepTwo" resultType="Dept">
select * from t_dept where did = #{did}
</select>
7.4映射关系:一对多
一对多:对应集合,返回一个集合
处理一对多的映射关系:
- collection标签处理
- 分步查询
7.4.1通过collection解决一对多的映射关系
Demp实体类中:
//用一个集合来表示所有的员工信息
private List<Emp> emps;
get()方法
set()方法
重写toString()方法
mapper中:
//获取部门以及部门中所有的员工信息
Dept getDeptAndEmp(@Param("did") Integer did);
mapper.xml中:
collection:处理一对多的映射关系
- ofType:表示该属性所对应的集合中存储数据的类型
<resultMap id="deptAndEmpResultMap" type="Dept">
<id property="did" column="did" />
<result property="deptName" column="dept_name" />
<collection property="emps" ofType="Emp">
<id property="eid" column="eid" />
<result property="empName" column="emp_name" />
<result property="age" column="age" />
<result property="sex" column="sex" />
<result property="email" column="u_email" />
<collection/>
</resultMap>
<select id="getDeptAndEmp" resultMap="deptAndEmpResultMap">
select * from t_dept left join t_emp on t_dept.did = t_emp.did
where t_dept.did = #{did}
</select>
测试类:
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Dept dept = mapper.getDeptAndEmp(1);
sout(dept);
}
7.4.2通过分步查询解决一对多的映射关系
mapper中:
//DeptMapper中:
//通过分步查询查询部门以及部门中所有的员工信息
//分步查询第一步:查询部门信息
Dept getDeptAndEmpByStepOne(@Param("did") Integer did);
//EmpMapper中:
//通过分步查询查询部门以及部门中所有的员工信息
//分步查询第二步:根据did查询员工信息
List<Emp> getDeptAndEmpByStepTwo(@Param("did") Integer did)
mapper.xml中:
<resultMap id="deptAndEmpByStepResultMap" type="Dept">
<id property="did" column="did" />
<result property="deptName" column="dept_name" />
<collection property="emps"
select="com.....EmpMapper.getDeptAndEmpByStepTwo"
column="did">
<collection/>
</resultMap>
<select id="getDeptAndEmpByStepOne" resultType="deptAndEmpByStepResultMap">
select * from t_dept where did = #{did}
</select>
<!--EmpMapper.xml中:-->
<select id="getDeptAndEmpByStepTwo" resultType="Emp">
select * from t_emp where did = #{did}
</select>
测试类:
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Dept dept = mapper.getDeptAndEmp(1);
sout(dept);
}
8.动态SQL
MyBatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题
8.1if标签
if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中的内容不会执行。
if标签:
- 根据标签中test属性所对应的表达式决定标签中的内容是否需要拼接到SQL中
mapper中:
//多条件查询
List<Emp> getEmpByCondition(Emp emp);
mapper.xml中:
where后面加上"1=1"的恒成立条件,避免出现如果第一条if判断不执行时,后面的语句直接执行where and报错
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp where 1=1
<if test="empName != null and empName !=''">
emp_name = #{empName}
</if>
<if test="age != null and age !=''">
and age = #{age}
</if>
<if test="sex != null and sex !=''">
and sex = #{sex}
</if>
<if test="email != null and email !=''">
and email = #{email}
</if>
</select>
测试类:
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Emp emp = mapper.getEmpByCondition(null,"张三",23,"男","123@qq.com");
sout(dept);
}
8.2where标签
上述if标签中的where关键字是写死了,避免报错需要加上一个1=1恒成立条件,但是Mybatis提供where标签来动态生成where关键字,还会自动去掉多余的and/or,不用额外加条件
where标签:
- 当where标签中有内容时,会自动生成where关键字,并且将内容前多余的and或or去掉
- 注意:where标签不能将其中内容后面多余的and或or去掉,使用时最好在前面加and或or
- 当where标签中没有内容时,此时where标签没有任何效果
mapper.xml中:
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<where>
<if test="empName != null and empName !=''">
emp_name = #{empName}
</if>
<if test="age != null and age !=''">
and age = #{age}
</if>
<if test="sex != null and sex !=''">
and sex = #{sex}
</if>
<if test="email != null and email !=''">
and email = #{email}
</if>
</where>
</select>
8.3trim标签
上述使用where标签可以去掉内容前面多余的and或or,但是无法去掉内容后面多余的and或or,下面可以用trim标签来实现在内容前面或后面添加/去掉指定内容
trim标签:
- 若标签中有内容时:
- prefix|suffix:将trim标签中内容前面或后面添加指定内容
- prefixOverrides|suffixOverrides:将trim标签中内容内容前面或后面去掉指定内容
- 若标签中没有内容时:trim标签也没有任何效果
mapper.xml中
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and|or">
<if test="empName != null and empName !=''">
emp_name = #{empName}
</if>
<if test="age != null and age !=''">
and age = #{age}
</if>
<if test="sex != null and sex !=''">
and sex = #{sex}
</if>
<if test="email != null and email !=''">
and email = #{email}
</if>
</trim>
</select>
8.4choose、when、otherwise标签
choose、when、otherwise是一套标签,相当于if…else if…else
-
choose相当于父标签,表示if…else if…else组合
-
when相当于else if,至少要有一个
-
otherwise相当于else,最多只能有一个
mapper中:
//测试choose、when、otherwise
List<Emp> getEmpByChoose(Emp emp);
mapper.xml中:
注意:只会执行满足条件的一条when,不用加and或or
<select id="getEmpByChoose" resultType="Emp">
select * from t_emp
<where>
<choose>
<when test="empName != null and empName !=''">
emp_name = #{empName}
</when>
<when test="age != null and age !=''">
age = #{age}
</when>
<when test="sex != null and sex !=''">
sex = #{sex}
</when>
<when test="email != null and email !=''">
email = #{email}
</when>
<otherwise>
did = 1
</otherwise>
</choose>
</where>
</select>
测试类:
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<Emp> empList = mapper.getEmpByChoose(null,"张三",23,"男","123@qq.com");
sout(empList);
}
8.5foreach标签
用来做循环操作,如批量删除或批量添加
foreeach标签:
- collection:设置需要循环的数组或集合
- item:表示数组或集合中的每一个数据
- separator:循环体之间的分隔符
- open:foreach标签所循环的所有内容的开始符
- close:foreach标签所循环的所有内容的结束符
mapper中:
//通过数组实现批量删除
int deleteMoreByArray(@Param("eids") Integer[] eids);
//通过List集合实现批量添加
int insertMoreByList(@Param("emps") List<Emp> emps);
mapper.xml中:
<!--批量删除第一种写法where id in (...),分隔符用","-->
<delete id="deleteMoreByArray">
delete from t_emp where eid in
(
<foreach collection="eids" item="eid" separator=",">
#{eid}
</foreach>
)
</delete>
<!--将左右括号放到foreach标签中用open和close来注明,表示循环体外加括号-->
<delete id="deleteMoreByArray">
delete from t_emp where eid in
<foreach collection="eids" item="eid" separator="," open="(" close=")">
#{eid}
</foreach>
</delete>
<!--批量删除第二种写法where id=? or id=?,分隔符用"or"-->
<delete id="deleteMoreByArray">
delete from t_emp where
<foreach collection="eids" item="eid" separator="or">
eid = #{eid}
</foreach>
</delete>
<insert id="insertMoreByList">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null,#{emp.empName},#{emp.age},#{emp.sex},#{emp.email},null)
</foreach>
</insert>
测试类:
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int result = mapper.deleteMoreByArray(new Integer[]{6,7,8});
sout(result);
}
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Emp emp1 = new Emp(null,"a1",23,"男","123@qq.com");
Emp emp2 = new Emp(null,"a2",23,"男","123@qq.com");
Emp emp3 = new Emp(null,"a3",23,"男","123@qq.com");
List<Emp> emps = Arrays.cList(emp1,emp2,emp3);
int result = mapper.insertMoreByList(emps);
sout(result);
}
8.6sql标签
将常用的sql片段拿出来进行记录,当需要用的时候直接引用
sql标签:
- 设置sql片段:eid,emp_name,age,sex,email
- 引用sql片段:
mapper.xml中:
<sql id="empColums">
eid,emp_name,age,sex,email
</sql>
<!--用include标签引入sql片段-->
<select id="getEmpByCondition" resultType="Emp">
select <include refid="empColums"></include> from t_emp
<trim prefix="where" suffixOverrides="and|or">
<if test="empName != null and empName !=''">
emp_name = #{empName}
</if>
<if test="age != null and age !=''">
and age = #{age}
</if>
<if test="sex != null and sex !=''">
and sex = #{sex}
</if>
<if test="email != null and email !=''">
and email = #{email}
</if>
</trim>
</select>
9.MyBatis缓存
将当前查询出的数据进行记录,等下一次再查询相同数据的时候,直接从缓存中取,不用重新访问数据库
MyBatis中的缓存分为一级缓存和二级缓存,一级缓存是默认开启的
9.1一级缓存SqlSession
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问
如:使用同一个SqlSession连续查询相同的数据,则只会执行一条SQL,其余的数据从缓存中拿取返回
@Test
public void test(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Emp emp1 = mapper.getEmpById(1);
sout(emp1);
Emp emp2 = mapper.getEmpById(1);
sout(emp2);
}
//执行效果:一条SQL语句,两条打印结果(emp1,emp2),后面那条是直接从缓存拿的,不用重新执行一次SQL
使一级缓存失效的四种情况:
-
不同的SqlSession对应不同的一级缓存
-
同一个SqlSession但是查询条件不同
-
同一个SqlSession两次查询期间执行了任何一次增删改操作
-
同一个SqlSession两次查询期间手动清空了缓存:使用sqlSession.clearCache()方法
@Test public void test(){ SqlSession sqlSession = SqlSessionUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Emp emp1 = mapper.getEmpById(1); sout(emp1); //清空缓存 sqlSession.clearCache() Emp emp2 = mapper.getEmpById(1); sout(emp2); } //执行效果:一条SQL,一条打印数据;再一条SQL,再一条打印数据
9.2二级缓存SqlSessionFactory
二级缓存的范围比一级缓存的范围要大一些,且需要手动设置
9.2.1二级缓存概念
二级缓存是SqlSessionFactory级别的,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
二级缓存开启的条件:4个条件缺一不可
- 在核心配置文件钟工,设置全局配置属性cacheEnabled=“true”,默认为true,不需要设置
- 在映射文件中设置标签,直接放在mapper.xml文件中即可
- 二级缓存必须在SqlSession关闭或提交之后有效
- 查询的数据所转换的实体类类型必须实现序列化的接口,implements Serializable
使二级缓存失效的情况:
- 唯一情况:两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
- 注意:sqlSession.clearCache()手动清空缓存只对一级缓存有效,无法使二级缓存失效
9.2.2二级缓存功能用法
二级缓存的用法测试如下:
全局配置中:默认cacheEnabled=“true”,不需要特别设置
实体类中:需要实现序列化接口
public class Emp implements Serializable{}
mapper.xml中:直接使用标签来开启二级缓存
<cache />
<select>
select * .......
</select>
需要测试二级缓存,此时就不要使用封装SqlSessionUtils工具类了,在测试方法中重新实现,一个sqlSessionFactory对应创建多个sqlSession来进行测试
注意:每个sqlSession执行完后必须使用sqlSession.close()来关闭才会有效,否则缓存命中率Cache Hit Ratio为0,二级缓存无效;加上sqlSession.close()后,第二个sqlSession的缓存命中率Cache Hit Ratio为0.5,不需要重新执行SQL,从二级缓存中拿取数据。
@Test
public void testCache() throws IOException{
try{
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
sout(mapper1.getEmpById(1));
sqlSession1.close();//需要关闭或提交后二级缓存才会有效,否则缓存命中率Cache Hit Ratio为0
SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
sout(mapper1.getEmpById(1));
sqlSession2.close();
} catch(IOException e){
e.printStackTrace();
}
}
9.2.3二级缓存相关配置
在mapper配置文件中添加的cache标签可以设置一些属性:
-
eviction属性:缓存回收策略
- LRU(Least Recently Used)-最近最少使用的:移除最长时间不被使用的对象
- FIFO(Fist in First out)-先进先出:按对象进入缓存的顺序来移除它们
- SOFT -软引用:移除基于垃圾回收器状态和软引用规则的对象
- WEAK -弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
默认的是LRU
-
flushInterval属性:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
-
size属性:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出
-
readOnly属性:只读,true/false
- true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改,这提供了很重要的性能优势
- false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false
9.3缓存查询顺序
MyBatis缓存查询的顺序如下:
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- SqlSession关闭之后,一级缓存中的数据会写入二级缓存
9.4整合第三方缓存EHCache
MyBatis是持久层框架,做缓存不太专业,提供了第三方接口来做二级缓存,但是一级缓存还是原生
9.4.1依赖导入
引入依赖:
<!--Mybatis EHCache整合包-->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
<!--slf4j日志门面的一个具体实现-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
各jar包功能:
- mybatis-ehcache:MyBatis和EHCache的整合包
- ehcache:EHCache核心包
- slf4j-api:SLF4J日志门面包
- logback-classic:支持SLF4J门面接口的一个具体实现
9.4.2EHCache配置
创建EHCache的配置文件ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\atguigu\ehcache" />
<defaultCache
maxElementsInMemory="10000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
在mapper.xml文件中使用设置type类型为EHCache缓存,不设置则默认使用MyBatis原生的二级缓存
<cache type="org.mybatis.caches.ehcache.EHCacheCache">
9.4.3加入logback日志
存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志
创建logback的配置文件logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、执行 -->
<pattern>[%d{HH:mm:ss.SSS}] [%-Sleve] [%thread] [$logger] [%msg]%n</pattern>
</encoder>
</appender>
<!--设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR-->
<!--指定任何一个日志级别都只打印当前级别和后面级别的日志-->
<root level="DEBUG">
<!--指定答应日志的appender,这里通过"STDOUt"引用了前面配置的appender-->
<appender-ref ref="STUOUT" />
</root>
<!--根据特殊需求指定局部日志级别-->
<logger name="com.atguigu.crowd.mapper" level="DEBUG">
</configuration>
到此,第三方缓存就配置完成了,像以前使用MyBatis原生的缓存一样使用即可,示例详见原二级缓存的使用
10.MyBatis逆向工程
10.1正向/逆向工程
-
正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的
-
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
- Java实体类
- Mapper接口
- Mapper映射文件
逆向工程主要用来自动生成代码
10.2逆向工程的配置&使用
添加依赖和插件:
<!--依赖MyBatis核心包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!--控制Maven在构建过程中相关配置-->
<build>
<!--构建过程中用到的插件-->
<plugins>
<!--具体插件,逆向工程的操作是以构建过程中插件形式出现的-->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<!--插件的依赖-->
<dependencies>
<!--逆向工程的核心依赖-->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<!--数据库连接池-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.2</version>
</dependency>
<!--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.8</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
创建配置文件:文件名必须是generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime:执行生成的逆向工程的版本
MyBatis3Simple:生成基本的CRUD(清新简洁版)
MyBatis3:生成带条件的CRUD(奢华尊享版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3">
<!-- 数据库的连接信息 -->
<!-- 数据库连接驱动类,URL,用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/mybatis"
userId="root"
password="123456">
</jdbcConnection>
<!-- javaBean的生成策略 -->
<!-- 生成Domain模型:包名(targetPackage)、位置(targetProject) -->
<javaModelGenerator targetPackage="com.atguigu.mybatis.domain" targetProject="\src\main\java">
<!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false -->
<property name="enableSubPackages" value="true"/>
<!-- 设置是否在getter方法中,对String类型字段调用trim()方法-->
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<!-- 生成xml映射文件:包名(targetPackage)、位置(targetProject) -->
<sqlMapGenerator targetPackage="com.atguigu.mybatis.mapper" targetProject="\src\main\resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<!-- 生成DAO接口:包名(targetPackage)、位置(targetProject) -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mybatis.mapper" targetProject="\src\main\java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName-->
<!-- domainObjectName属性指定生成出来的实体类的类名-->
<!-- 要生成的表:tableName - 数据库中的表名或视图名,domainObjectName - 实体类名 -->
<table tableName="t_emp" domainObjectName="Emp" />
<table tableName="t_dept" domainObjectName="EmpDept />
</context>
</generatorConfiguration>
以上配置完成后,在IDEA右上角的maven工程里面找到mybatis-generator:generate插件,然后直接双击运行,成功后即可生成文件
10.3奢华尊享版代码使用
在上述generatorConfig.xml配置中,我们可以生成两种代码,其中MyBatis3Simple生成后只有基础CRUD代码,MyBatis3生成后会在domain里面额外增加EmpExample、DeptExample类,用来实现条件查询
MyBatis3Simple:生成基本的CRUD(清新简洁版)
MyBatis3:生成带条件的CRUD(奢华尊享版)
奢华尊享版查询功能测试:
@Test
public void test() throws IOException{
try{
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//查询所有数据,无条件查询,传入值为null
List<Emp> list = mapper.selectByExample(null);
list.foreach(emp->sout(emp));
//根据条件查询,首先创建一个EmpExample
EmpExample example = new EmpExample();
//使用createCriteria()创造条件,后面带上字段条件,且可以通过.链式结构一直加条件,下面表示查询姓名为“张三”且年龄大于等于20的信息
example.createCriteria().andEmpNameEqualTo("张三").andAgeGreaterThanOrEqualTo(20);
//如果想用or条件,直接用or(),后面继续跟andXXX条件,即表示与上一个条件是or关系
example.or().andDidIsNotNull();
//将example放入查询中,即实现带条件查询
List<Emp> list = mapper.selectByExample(example);
list.foreach(emp->sout(emp));
} catch(IOException e){
e.printStackTrace();
}
}
奢华尊享版修改功能测试:
@Test
public void test() throws IOException{
try{
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//根据主键进行修改
mapper.uodateByPrimaryKey(1,"admin",22,null,"456@qq,com");//null也会直接修改null
mapper.uodateByPrimaryKeySelective(1,"admin",22,null,"456@qq,com");//选择性修改,null不会修改,直接忽略
} catch(IOException e){
e.printStackTrace();
}
}
11.MyBatis分页插件
11.1分页插件使用步骤
导入依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
配置分页插件:
在MyBatis的核心配置文件中配置插件
<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
11.2分页插件的使用
数据库分页查询关键字:limit index,pageSize
MyBatis分页插件使用:page(pageNum,pageSize)
- index:当前页的起始索引
- pageSize:每页显示的条数
- pageNum:当前页的页码
- index=(pageNum-1)*pageSize
使用MyBatis的分页插件实现分页功能:
-
需要在查询功能之前开启分页:
PageHelper.startPage(int pageNum,int pageSize);
-
在查询分页之后获取分页相关信息:
PageInfo page = new PageInfo<>(list,5);
- list:表示分页数据
- 5:表示当前导航分页的数据,显示5个导航分页,建议奇数,左边展示2个,右边展示2个
常用数据:
- pageNum:当前页的页码
- pageSize:每页显示的条数
- size:当前页显示的条数
- total:总记录数
- pages:页数
- prePage:上一页的页码
- nextPage:下一页的页码
- isFirstPage/isLastPage:是否为第一页/最后一页
- hasPreviousPage/hasNextPage:是否存在上一页/下一页
- navigatePage:导航分页的页码数
- navigatepageNums:导航分页的页码,[1,2,3,4,5]
@Test
public void test() throws IOException{
try{
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//查询之前开启分页插件,访问第1页,显示4条数据,返回值是一个page对象
PageHelper.startPage(1,4);
List<Emp> list = mapper.selectByPage(null);
PageInfo<Emp> page = new PageInfo<>(list,5);
list.foreach(emp->sout(emp));
} catch(IOException e){
e.printStackTrace();
}
}
public void test() throws IOException{
try{
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//根据主键进行修改
mapper.uodateByPrimaryKey(1,"admin",22,null,"456@qq,com");//null也会直接修改null
mapper.uodateByPrimaryKeySelective(1,"admin",22,null,"456@qq,com");//选择性修改,null不会修改,直接忽略
} catch(IOException e){
e.printStackTrace();
}
}
11.MyBatis分页插件
11.1分页插件使用步骤
导入依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
配置分页插件:
在MyBatis的核心配置文件中配置插件
<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
11.2分页插件的使用
数据库分页查询关键字:limit index,pageSize
MyBatis分页插件使用:page(pageNum,pageSize)
- index:当前页的起始索引
- pageSize:每页显示的条数
- pageNum:当前页的页码
- index=(pageNum-1)*pageSize
使用MyBatis的分页插件实现分页功能:
-
需要在查询功能之前开启分页:
PageHelper.startPage(int pageNum,int pageSize);
-
在查询分页之后获取分页相关信息:
PageInfo page = new PageInfo<>(list,5);
- list:表示分页数据
- 5:表示当前导航分页的数据,显示5个导航分页,建议奇数,左边展示2个,右边展示2个
常用数据:
- pageNum:当前页的页码
- pageSize:每页显示的条数
- size:当前页显示的条数
- total:总记录数
- pages:页数
- prePage:上一页的页码
- nextPage:下一页的页码
- isFirstPage/isLastPage:是否为第一页/最后一页
- hasPreviousPage/hasNextPage:是否存在上一页/下一页
- navigatePage:导航分页的页码数
- navigatepageNums:导航分页的页码,[1,2,3,4,5]
@Test
public void test() throws IOException{
try{
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//查询之前开启分页插件,访问第1页,显示4条数据,返回值是一个page对象
PageHelper.startPage(1,4);
List<Emp> list = mapper.selectByPage(null);
PageInfo<Emp> page = new PageInfo<>(list,5);
list.foreach(emp->sout(emp));
} catch(IOException e){
e.printStackTrace();
}
}