MyBatis
面试题:mybatis与hibernate区别?
-
Hibernate 和 MyBatis 都是目前业界中主流的对象关系映射(ORM)框架
-
sql优化方面
- Hibernate 使用 HQL(Hibernate Query Language)语句,独立于数据库。不需要编写大量的 SQL,就可以完全映射,但会多消耗性能,且开发人员不能自主的进行 SQL 性能优化。提供了日志、缓存、级联(级联比 MyBatis 强大)等特性。
- MyBatis 需要手动编写 SQL,所以灵活多变。支持动态 SQL、处理列表、动态生成表名、支持存储过程。工作量相对较大。
-
开发方面
-
MyBatis 是一个半自动映射的框架,因为 MyBatis 需要手动匹配 POJO 和 SQL 的映射关系。
Hibernate 是一个全表映射的框架,只需提供 POJO 和映射关系即可。
-
-
针对简单逻辑,Hibernate和MyBatis都有相应的代码生成工具,可以生成简单基本的DAO层方法。
针对高级查询,Mybatis需要手动编写SQL语句,以及ResultMap。而Hibernate有良好的映射机制,开发者无需关心SQL的生成与结果映射,可以更专注于业务流程。
-
Hibernate 有更好的二级缓存机制,可以使用第三方缓存。MyBatis 本身提供的缓存机制不佳。
-
MyBatis 适合需求多变的互联网项目,Hibernate 适合需求明确、业务固定的项目。
1、Mybatis概述
——MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发,内部封装了 jdbc
MyBatis 本是 Apache 的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github
官网:https://mybatis.org/mybatis-3/zh/index.html
问题提出:
(1)什么是持久层?
- JavaEE三层架构:表现层、业务层、持久层
- 持久层就是负责操作数据库的那一层代码,以后开发我们会将操作数据库的Java代码作为持久层,而Mybatis就是对jdbc代码进行了封装。
(2)什么是框架?
- 框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型
- 在框架的基础之上构建软件编写更加高效、规范、通用、可扩展
1.1、 JDBC 缺点
下面是 JDBC 代码,我们通过该代码分析都存在什么缺点:
- 硬编码
- 注册驱动、获取连接存在硬编码问题,如果换成别的关系型数据库,需要更改源代码参数。
- 代码冗余
- 业务操作和数据库操作混在一起,开发效率很低。
- 操作繁琐
-
需要我们手动设置参数
-
ResultSet 不能帮我们完成数据库和实体对象的自动转换,我们还要手动赋值。
-
1.2、Mybatis 优化
-
MyBatis内部封装了 jdbc,开发人员只需要关注 sql 语句,不需要处理加载驱动、创建连接等繁琐的过程(配置xml文件,交给mybatis处理即可)。
-
MyBatis 通过 xml 或 注解 两种方式配置 sql 语句,然后执行 sql 并将结果映射为 java 对象并返回。
2、Mapper代理开发(重点掌握)
需求:查询user表中所有的数据
步骤1:创建user表,添加数据
create database mybatisTest collate utf8_general_ci character set utf8;
use mybatisTest;
drop table if exists tb_user;
create table tb_user(
id int primary key auto_increment,
username varchar(20),
password varchar(20),
gender char(1),
addr varchar(30)
);
INSERT INTO tb_user VALUES (1, 'zhangsan', '123', '男', '北京');
INSERT INTO tb_user VALUES (2, '李四', '234', '女', '天津');
INSERT INTO tb_user VALUES (3, '王五', '11', '男', '西安');
步骤2:创建User实体类
——一般根据数据库创建
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String username;
private String password;
private String gender;
private String addr;
}
步骤3:创建UserMapper接口,并编写方法
-
在
com.itheima.mapper
包下创建 UserMapper接口,代码如下:public interface UserMapper { //查询所有用户数据 List<User> selectAll(); }
步骤4:创建模块,导入坐标
<dependencies>
<!--mybatis 依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!--mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!--junit 单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!--mybatis分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<!--解决控制台乱码-->
<dependency>
<groupId>org.jboss</groupId>
<artifactId>jboss-vfs</artifactId>
<version>3.2.16.Final</version>
</dependency>
<!-- 添加这一层依赖可以在控制台打印sql语句执行信息-->
<!-- 添加slf4j日志api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.20</version>
</dependency>
<!-- 添加logback-classic依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- 添加logback-core依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
</dependencies>
步骤5:编写 MyBatis 核心配置文件
在模块下的 resources 目录下创建mybatis的配置文件 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>
<settings>
<!--是否开启全局二级缓存-->
<setting name="cacheEnabled" value="true"/>
<!-- 是否在控制台打印日志 -->
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
<!--给全路径名起一个简单的别名,一般是实体类的类名,简化映射文件的resultType编写-->
<typeAliases>
<package name="com.haoyu.pojo"/>
</typeAliases>
<plugins>
<!--分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
<!--
environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment
-->
<environments default="development">
<!-- 配置 mysql 的环境-->
<environment id="development">
<!--事务的类型,JDBC类型-->
<transactionManager type="JDBC"/>
<!-- 配置数据源 POOLED:表示使用连接池 -->
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql:///mybatisTest?useSSL=false&useServerPrepStmts=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="232104"/>
</dataSource>
</environment>
</environments>
<!-- 指定映射配置文件的位置-->
<mappers>
<!--Mapper代理模式加载sql映射文件-->
<package name="com.haoyu.mapper"/>
</mappers>
</configuration>
步骤6:编写 SQL 映射文件(统一管理sql语句)
-
规定:文件名称要和接口保持一致,Mapper接口需要和SQL映射文件放置在同一目录下。(userMapper接口对应userMapper.xml)
-
一般来说一个表对应一个xml
在模块的 resources
目录下创建映射配置文件 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">
<!--
namespace:名称空间,必须是对应接口的全限定名
id:唯一标识,在 Mapper 接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致
resultType:返回结果的类型
-->
<mapper namespace="com.haoyu.mapper.UserMapper">
<select id="selectAll" resultType="User">
select * from tb_user;
</select>
</mapper>
特别注意:
1、因为mybatis核心配置文件取了别名,resultType可以直接简写成resultType=“User”>,如果未取应该resultType=“com.itheima.pojo.User”>
2、resultType一般值为实体类类型,如果方法没有返回值可以不写。(语句的返回结果就是以这个为准)
步骤7:创建UserMapper接口,并编写方法
-
在
com.itheima.mapper
包下创建 UserMapper接口,代码如下:public interface UserMapper { //查询所有用户数据 List<User> selectAll(); }
步骤8:创建测试类
/**
* Mybatis 代理开发
*/
public class MyBatisDemo{
public static void main(String[] args) throws IOException {
//1. 加载mybatis的核心配置文件,获取 SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象,用它来执行sql
SqlSession sqlSession = sqlSessionFactory.openSession();
//3. 执行sql
//3.1 获取UserMapper接口的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectAll();
for (User user : users) {
System.out.println(user.toString());
}
//4. 释放资源
sqlSession.close();
}
}
代码优化:抽取工具类
——因为获取sqlSessionFactory的代码可以重复利用,抽取出来当作工具类
public class SqlSessionFactoryUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
//静态代码块会随着类的加载而自动执行,且只执行一次
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSessionFactory getSqlSessionFactory(){
return sqlSessionFactory;
}
}
优化步骤8
public class MybatisDemo {
//通过工具类方法加载mybatis的核心配置文件,获取SqlSessionFactory
public static SqlSessionFactory factory= SqlSessionFactoryUtils.getSqlSessionFactory();
public static void main(String[] args) throws IOException {
//获取SqlSession对象,用它来执行sql
SqlSession sqlSession = factory.openSession();
//获取UserMapper接口的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//执行sql
List<User> users = userMapper.selectAll();
for (User user : users) {
System.out.println(user.toString());
}
//释放资源
sqlSession.close();
}
}
扩展:SqlSessionFactory的作用(重点)
——SqlSessionFactory是MyBatis中的一个工厂类,用于创建SqlSession对象。它是MyBatis框架中最重要的对象之一,负责管理MyBatis的配置信息和映射信息,并根据这些信息创建SqlSession对象,进而与数据库进行交互。
SqlSessionFactory的创建和使用流程一般如下所示:
- 读取MyBatis的配置文件:SqlSessionFactory从MyBatis的配置文件中读取配置信息。
- 创建数据库连接池:SqlSessionFactory根据配置信息创建数据库连接池,以提高数据库操作的性能。
- 创建SqlSessionFactory对象:SqlSessionFactory根据配置信息创建SqlSessionFactory对象,用于创建SqlSession对象。
- 创建SqlSession对象:应用程序通过SqlSessionFactory创建SqlSession对象,用于执行SQL语句、提交事务等操作。
- 执行数据库操作:通过SqlSession对象执行数据库操作,如查询、插入、更新、删除等。
- 提交事务或回滚事务:如果应用程序需要提交事务或回滚事务,则调用SqlSession的commit或rollback方法。
- 关闭SqlSession:操作完成后,应用程序需要调用SqlSession的close方法关闭SqlSession对象,释放数据库连接等资源。
扩展:关于mybatis的事务控制
SqlSession sqlSession = sqlSessionFactory.openSession(false); //关闭自动提交事务
try {
// 执行数据库操作
sqlSession.insert("insertUser", user);
sqlSession.update("updateUser", user);
// 提交事务
sqlSession.commit();
} catch (Exception e) {
// 回滚事务
sqlSession.rollback();
} finally {
// 关闭 SqlSession
sqlSession.close();
}
3、使用Mapper代理要求
- 定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下。
-
设置SQL映射文件的namespace属性为Mapper接口全限定名
-
在 Mapper 接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致。
4、Mybatis深入了解
扩展:安装 MyBatisX 插件
功能:
- 红色头绳的表示映射配置文件,蓝色头绳的表示mapper接口,点击小鸟可以实现相互跳转。
- 自动生成statement
4.1、实现CRUD操作(重点)
——特别注意:涉及到增删改操作时,一定要提交事务。
4.1.1、数据准备
brand数据表:
-- 删除tb_brand表
drop table if exists tb_brand;
-- 创建tb_brand表
create table tb_brand
(
-- id 主键
id int primary key auto_increment,
-- 品牌名称
brand_name varchar(20),
-- 企业名称
company_name varchar(20),
-- 排序字段
ordered int,
-- 描述信息
description varchar(100),
-- 状态:0:禁用 1:启用
status int
);
-- 添加数据
insert into tb_brand (brand_name, company_name, ordered, description, status)
values ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),
('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界', 1),
('小米', '小米科技有限公司', 50, 'are you ok', 1);
实体类Brand
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Brand {
// id 主键
private Integer id;
// 品牌名称
private String brandName;
// 企业名称
private String companyName;
// 排序字段
private Integer ordered;
// 描述信息
private String description;
// 状态:0:禁用 1:启用
private Integer status;
}
4.1.2、数据库字段名与实体类不匹配问题
——因为mybatis底层获取数据后,会通过反射创建对应对象(如Brand)并封装数据,如果数据库字段名与实体类不匹配会导致封装失败,从而导致不对应数据为null。
方式1:使用SQL片段
-
将需要复用的SQL片段抽取到
sql
标签中<sql id="brand_column"> id, brand_name as brandName, company_name as companyName, ordered, description, status </sql>
id属性值是唯一标识,引用时也是通过该值进行引用。
-
在原sql语句中进行引用
使用
include
标签引用上述的 SQL 片段,而refid
指定上述 SQL 片段的id值。<select id="selectAll" resultType="brand"> select <include refid="brand_column" /> from tb_brand; </select>
方式2:使用resultMap解决(推荐使用)
-
在映射配置文件中使用resultMap定义 字段 和 属性 的映射关系
案例:
<mapper namespace="com.haoyu.mapper.BrandMapper"> <!-- type表示返回值类型 --> <resultMap id="brandResultMap" type="brand"> <!-- id:完成主键字段的映射 column:表的列名 property:实体类的属性名 result:完成一般字段的映射 column:表的列名 property:实体类的属性名 --> <!--也就是告诉mybatis将查询到的brand_name的值赋值给 反射创建的对象Brand的属性brandName --> <result column="brand_name" property="brandName"/> <result column="company_name" property="companyName"/> </resultMap> <select id="selectAll" resultMap="brandResultMap"> //特别注意:这个时候应该使用resultMap select * from tb_brand; </select> </mapper>
- 配置后SQL语句正常编写即可
- 特别注意:这个时候resultType=“Brand"应该改为resultMap=” resultMap的id名 ">
4.1.3、提供id查询数据
1、编写接口方法
//根据Id查询
Brand selectById(int id);
2、编写SQL语句
<select id="selectById" resultMap="brandResultMap">
select *
from tb_brand where id = #{id};
</select>
//注意:因为使用了resultMap映射关系,需使用'resultMap'而不是使用'resultType'
4.1.3.1、参数占位符
mybatis提供了两种参数占位符:
- #{} :执行SQL时,会将 #{} 占位符替换为?,将来自动设置参数值,底层使用的是
PreparedStatement
。 - ${}:直接将数据拼接到SQL语句中,有注入风险,不推荐使用。
4.1.3.2、parameterType使用
对于有参数的mapper接口方法,我们在映射配置文件中应该配置 ParameterType
来指定参数类型。只不过该属性都可以省略
<select id="selectById" parameterType="int" resultMap="brandResultMap">
select *
from tb_brand where id = ${id};
</select>
4.1.3.3、SQL语句中特殊字段处理
4.1.4、多条件查询(重点)
步骤1:编写接口方法
而该功能有三个参数,我们就需要考虑定义接口时,参数应该如何定义。Mybatis针对多参数有多种实现:
-
使用
@Param("参数名称")
标记每一个参数,在映射配置文件中就需要使用#{参数名称}
进行占位。List<Brand> selectByCondition(@Param("status") int status, @Param("companyName") String companyName,@Param("brandName") String brandName);
-
将多个参数封装成一个 实体对象 ,将该实体对象作为接口的方法参数。该方式要求在映射配置文件的SQL中使用
#{内容}
时,里面的内容必须和实体类属性名保持一致。List<Brand> selectByCondition(Brand brand);
-
将多个参数封装到map集合中,将map集合作为接口的方法参数。该方式要求在映射配置文件的SQL中使用
#{内容}
时,里面的内容必须和map集合中键的名称一致。List<Brand> selectByCondition(Map map);
步骤2:编写SQL语句
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
where status = #{status}
and company_name like #{companyName}
and brand_name like #{brandName}
</select>
步骤3:编写测试方法
//通过工具类方法加载mybatis的核心配置文件,获取SqlSessionFactory
public static SqlSessionFactory factory= SqlSessionFactoryUtils.getSqlSessionFactory();
//多条件查询
@Test
public void testSelectByCondition() throws IOException {
//接收参数
int status = 1;
String companyName = "华为";
String brandName = "华为";
//处理参数
companyName = "%" + companyName + "%"; //因为是模糊查询,所以要作处理
brandName = "%" + brandName + "%";
//获取SqlSession对象
SqlSession sqlSession = factory.openSession();
//获取Mapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//执行方法
方式一 :接口方法参数使用 @Param 方式调用的方法
List<Brand> brands = brandMapper.selectByCondition(status, companyName, brandName);
方式二 :接口方法参数是 实体类对象 方式调用的方法
//封装对象
Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);
List<Brand> brands = brandMapper.selectByCondition(brand);
方式三 :接口方法参数是 map集合对象 方式调用的方法
Map map = new HashMap();
map.put("status" , status);
map.put("companyName", companyName);
map.put("brandName" , brandName);
List<Brand> brands = brandMapper.selectByCondition(map);
System.out.println(brands);
//5. 释放资源
sqlSession.close();
}
扩展:@Param注解的作用
——给参数命名。
案例:
mapper中的方法
public User selectUser(@Param("userName") String name,@Param("password") String pwd);
映射到xml中的标签
<select id="selectUser" resultMap="User">
select * from user where user_name = #{userName} and user_password=#{password}
</select>
-
其中where user_name = #{userName} and user_password = #{password} 中的userName和password都是从注解@Param()里面取出来的,取出来的值就是方法中形式参数 String name 和 String pwd的值。`
-
也就是说,SQL语句占位符中名称要与@Param注解命名保持一致。
4.1.4.1、动态SQL(重点)
情况1:多个条件
——需要使用 if和where 标签实现
上述功能实现存在很大的问题:用户在输入条件时,可能输入条件不会都填写,这个时候SQL语句就不能那么填写:
用户只输入当前状态时,SQL语句就是:
select * from tb_brand where status = #{status}
而用户如果只输入企业名称时,SQL语句就是:
select * from tb_brand where company_name like #{companName}
而用户如果输入了 当前状态
和 企业名称
时,SQL语句又不一样:
select * from tb_brand where status = #{status} and company_name like #{companName}
针对上述的需要,Mybatis对动态SQL有很强大的支撑:
if
choose (when, otherwise)
trim (where, set)
foreach
-
if 标签:条件判断
- test 属性:逻辑表达式
-
where 标签
- 作用:
- 替换where关键字
- 会动态的去掉第一个条件前的 and
- 如果所有的参数没有值则不加where关键字
<select id="selectByCondition" resultMap="brandResultMap"> select * from tb_brand <where> <if test="status != null"> and status = #{status} </if> <if test="companyName != null and companyName != '' "> and company_name like #{companyName} </if> <if test="brandName != null and brandName != '' "> and brand_name like #{brandName} </if> </where> </select>
注意:if标签与where标签结合使用时,需要给每个条件前都加上 and 关键字。
- 作用:
如上的这种SQL语句就会根据传递的参数值进行动态的拼接,如果此时status和companyName有值那么就会值拼接这两个条件。
- choose标签
- 作用:相当于if,不过可以配合 when标签 和 otherwrise标签(相当于else)使用。
例子:
<choose>
<when test="status != null">
and status = #{status}
</when>
<otherwrise></otherwrise>
</choose>
//相当于java的
if(test="status != null"){
and status = #{status}
}else{
}
情况2:单个条件
——需要使用choose(when,otherwise)标签实现
如上图所示,在查询时只能选择 品牌名称
、当前状态
、企业名称
这三个条件中的一个,但是用户到底选择哪儿一个,我们并不能确定。这种就属于单个条件的动态SQL语句。
编写接口方法
//单条件查询
List<Brand> selectByConditionSingle(Brand brand);
编写SQL语句
<select id="selectByConditionSingle" resultMap="brandResultMap">
select *
from tb_brand
<where>
<choose><!--相当于switch-->
<when test="status != null"><!--相当于case-->
status = #{status}
</when>
<when test="companyName != null and companyName != '' "><!--相当于case-->
company_name like #{companyName}
</when>
<when test="brandName != null and brandName != ''"><!--相当于case-->
brand_name like #{brandName}
</when>
</choose>
</where>
</select>
编写测试方法
在 test/java
下的 com.itheima.mapper
包下的 MybatisTest类中
定义测试方法
//通过工具类方法加载mybatis的核心配置文件,获取SqlSessionFactory
public static SqlSessionFactory factory= SqlSessionFactoryUtils.getSqlSessionFactory();
@Test
public void testSelectByConditionSingle() throws IOException {
//接收参数
String companyName = "华为";
// 处理参数
companyName = "%" + companyName + "%";
//封装对象
Brand brand = new Brand();
brand.setCompanyName(companyName); //只有单一条件
//获取SqlSession对象
SqlSession sqlSession = factory.openSession();
//获取Mapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//4. 执行方法
List<Brand> brands = brandMapper.selectByConditionSingle(brand);
System.out.println(brands);
//5. 释放资源
sqlSession.close();
}
执行测试方法结果如下:
4.1.5、添加数据
编写接口方法
//添加数据
void add(Brand brand);
编写SQL语句
——因为id一般是自增,所以不用添加
<insert id="add">
insert into tb_brand (brand_name, company_name, ordered, description, status)
values (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status});
</insert>
编写测试方法
//通过工具类方法加载mybatis的核心配置文件,获取SqlSessionFactory
public static SqlSessionFactory factory= SqlSessionFactoryUtils.getSqlSessionFactory();
@Test
public void testAdd() throws IOException {
//接收数据并封装对象
Brand brand = new Brand("豪与她","豪与有限公司",20,"这是一家无敌的公司",1);
//获取SqlSession对象
SqlSession sqlSession = factory.openSession(true); //设置自动提交事务,这种情况不需要手动提交事务了
//获取Mapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//执行方法
brandMapper.add(brand);
//释放资源
sqlSession.close();
}
}
4.1.5.1、主键返回
——有时候我们添加完数据后会使用到其主键。
编写接口方法
//添加数据
int add(Brand brand);
编写SQL语句
在 insert 标签上添加如下属性:
- useGeneratedKeys:是够获取自动增长的主键值。true表示获取
- keyProperty :指定将获取到的主键值封装到哪儿个属性里
<insert id="add" useGeneratedKeys="true" keyProperty="id">
insert into tb_brand (brand_name, company_name, ordered, description, status)
values (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status});
</insert>
这个时候执行完方法后brand对象中就有其id值了
brandMapper.addData(brand);
int id=brand.getId();
4.1.6、修改数据
在前端页面修改数据时,如果输入框没有输入内容时,应该保留字段之前的值。
编写接口方法
//修改数据
void update(Brand brand);
编写SQL语句
<update id="update">
update tb_brand
<set>
<trim suffixOverrides=","> <!--用于去除多余的","-->
<if test="brandName != null and brandName != ''">
brand_name = #{brandName},
</if>
<if test="companyName != null and companyName != ''">
company_name = #{companyName},
</if>
<if test="ordered != null">
ordered = #{ordered},
</if>
<if test="description != null and description != ''">
description = #{description},
</if>
<if test="status != null">
status = #{status}
</if>
</trim>
</set>
where id = #{id}
</update>
编写测试方法
//通过工具类方法加载mybatis的核心配置文件,获取SqlSessionFactory
public static SqlSessionFactory factory= SqlSessionFactoryUtils.getSqlSessionFactory();
@Test
public void testUpdate(){
int id=4;
//封装对象
Brand brand = new Brand(null,null,10,"这是一家无敌的公司",2);
brand.setId(id);
//获取SqlSession对象
SqlSession sqlSession = factory.openSession(true);
//获取Mapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//4. 执行方法
brandMapper.updateDnata(brand);
//5. 释放资源
sqlSession.close();
}
4.1.7、批量删除数据
——主要使用的是foreach标签
foreach标签的主要属性:
- collection:
- collection=“list” 传入的参数是一个list集合
- collection=“array” 传入的参数是一个array集合
- collection=“map” 传入的参数是一个map集合
- **item:**循环的临时变量,相当于foreach遍历的临时变量名
- **separator:**每次循环的分隔符
- **open:**整个循环内容开头的字符串。
- **close:**整个循环内容结尾的字符串。
编写接口方法
void delete(Integer[] ids);
编写SQL语句
<delete id="delete" >
delete from tb_brand
<where>
id in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</where>
</delete>
注意:这条语句就相当于 delete from tb_brand where id in( , , , , );
编写测试方法
@Test
public void testDelete(){
Integer[] ids={1,2,3};
//获取SqlSession对象
SqlSession sqlSession = factory.openSession(true);
//获取Mapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//当有数据时才调用方法,避免无数据而出错
if(ids.length>0) brandMapper.delete(ids);
sqlSession.close();
}
5、使用注解开发
——注解是用来替换映射配置文件方式配置的,所以使用了注解,就不需要再映射配置文件中书写对应的 statement
。
Mybatis 针对 CURD 操作都提供了对应的注解:
- 查询 :@Select
- 添加 :@Insert
- 修改 :@Update
- 删除 :@Delete
在BrandMapper接口的 selectById方法上添加注解
@Select("select * from tb_brand where id=#{id}")
Brand selectById(int id);
测试方法和之前一样,直接使用即可。
总结:注解用于完成简单的SQL语句,对于复杂的操作,最好用xml来映射语句
6、mybatis分页操作
6.1、使用mybatis分页插件
添加依赖:
<!--mybatis分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<!--解决控制台乱码-->
<dependency>
<groupId>org.jboss</groupId>
<artifactId>jboss-vfs</artifactId>
<version>3.2.16.Final</version>
</dependency>
在MyBatis的核心配置文件中配置插件:
<plugins>
<plugin interceptor="com.github.pagehelper.PageHelper">
</plugin>
</plugins>
在查询功能之前使用PageHelper.startPage(int pageNum,int pageSize)开启分页功能:
pageNum:当前页的页码,从1开始
pageSize:每页显示的条数
@Test
public void selectAll() {
//获取SqlSession对象,用它来执行sql
SqlSession sqlSession = factory.openSession();
//获取UserMapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//开启分页拦截器,拦截器会自动在sql后面追加limit
PageHelper.startPage(1, 3);
//执行SQL方法
List<Brand> brands = brandMapper.selectAll();
brands.forEach(brand -> System.out.println(brand));
//获取分页相关数据
PageInfo<Brand> page = new PageInfo<>(brands);
System.out.println(page.getPageNum()); //获取当前页码
System.out.println(page.getSize()); //获取每页显示的条数
//释放资源
sqlSession.close();
}
在查询获取list集合之后,使用PageInfo pageInfo = new PageInfo<>(list list,int navigatePages)获取分页相关数据.
list:分页之后的数据
navigatePages:导航分页的页码数
常用数据:
pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真实条数
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
6.2、SpringBoot使用分页
-
导入坐标
<!--PageHelper坐标--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.5</version> </dependency>
-
编写代码
//分页查询文章 @Override public Result<PageBean<Article>> list(Integer pageNum, Integer pageSize, Integer categoryId, String state) { //创建PageBean对象 PageBean<Article> pageBean=new PageBean<>(); //开启分页拦截器,拦截器会自动在sql后面追加limit PageHelper.startPage(pageNum,pageSize); //调用mapper Map<String, Object> map=ThreadLocalUtil.get(); Integer userId = (Integer) map.get("id"); List<Article> lists= articleMapper.list(userId,categoryId,state); //Page中提供了方法,可以获取PageHelper分页查询后,得到的总记录条数和当前页数据 Page<Article> page= (Page<Article>) lists; pageBean.setTotal(page.getTotal()); pageBean.setItems(lists); return Result.success(pageBean); }
7、mybatis完成复杂查询(重点)
7.1、数据准备
#创建班级表
create table class
(
class_id int not null PRIMARY KEY AUTO_INCREMENT ,
class_name VARCHAR(15) not null
);
#创建学生表
create table student
(
stu_id int not null PRIMARY KEY AUTO_INCREMENT,
stu_name VARCHAR(15) not null,
class_id int not null
);
#创建一些数据
insert into class VALUES (1,'101班');
insert into class VALUES (2,'102班');
insert into student VALUES(1,'haoyu',1);
insert into student VALUES(2,'sijia',1);
insert into student VALUES(3,'tanlin',2);
insert into student VALUES(4,'xiaocui',2);
创建实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StuClass {
private Integer id;
private String name;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private Integer id;
private String name;
private Integer classId;
}
7.2、多对一(association)
——多个学生对应一个教室,多对一情况下,在Stdent类添加ClassRoom属性
public class Student {
private Integer id;
private String name;
private Integer classId;
//添加一方对象属性
private StuClass stuClass;
}
定义StudentMapper接口
public interface StudentMapper {
List<Student> selectAllStudent();
}
编写xml方法
方式一:
在StudentMapper.xml编写
<mapper namespace="com.haoyu.mapper.StudentMapper">
<select id="selectAllStudent" resultMap="selectAllRes">
select * from student s left join class c on s.class_id=c.class_id
</select>
<resultMap id="selectAllRes" type="Student">
<id column="stu_id" property="id"/>
<result column="stu_name" property="name"/>
<result column="class_id" property="classId"/>
<!-- property表示实体类的属性名,javaType表示属性的类型 -->
<association property="stuClass" javaType="StuClass">
<id column="class_id" property="id"/>
<result column="class_name" property="name"/>
</association>
</resultMap>
</mapper>
缺点:需要手动配置所有字段映射,不然会为null,比较适用于字段较少的查询。
结果:
方式二:
在StudentMapper.xml编写
<mapper namespace="com.haoyu.mapper.StudentMapper">
<select id="selectAllStudent" resultMap="selectAllRes">
select * from student;
</select>
<resultMap id="selectAllRes" type="Student">
<id column="stu_id" property="id"/>
<result column="stu_name" property="name"/>
<result column="class_id" property="classId"/>
<!-- 查询到学生信息时,会用column的值传递给select指定的查询selectClass,selectClass查询到的结果会赋值给property指定的成员变量 -->
<association property="stuClass" column="class_id"
<!-- 使用外部的语句-->
select="com.haoyu.mapper.ClassMapper.selectClass"></association>
</resultMap>
</mapper>
在ClassMapper.xml编写
<mapper namespace="com.haoyu.mapper.ClassMapper">
<select id="selectClass" resultMap="selectClassRes">
select * from class where class_id=#{class_id}
</select>
<resultMap id="selectClassRes" type="StuClass">
<id column="class_id" property="id"/>
<result column="class_name" property="name"/>
</resultMap>
</mapper>
结果:
7.2、一对多(collection)
——一个教师有多个学生,一对多情况下,在StuClass类添加List属性
public class StuClass {
private Integer id;
private String name;
//添加多方集合对象属性
private List<Student> students;
}
编写接口方法
public interface StudentMapper {
List<StuClass> selectAllClass();
}
编写xml方法
方式一:
在ClassMapper.xml编写
<mapper namespace="com.haoyu.mapper.ClassMapper">
<select id="selectAllClass" resultMap="selectAllClassRes">
select * from class c left join student s on s.class_id=c.class_id
</select>
<resultMap id="selectAllClassRes" type="StuClass">
<id column="class_id" property="id"/>
<result column="class_name" property="name"/>
<collection property="students" ofType="Student">
<id column="stu_id" property="id"/>
<result column="stu_name" property="name"/>
<result column="class_id" property="classId"/>
</collection>
</resultMap>
</mapper>
结果:
方式二:
在ClassMapper.xml编写
<mapper namespace="com.haoyu.mapper.ClassMapper">
<select id="selectAllClass" resultMap="selectAllClassRes">
select * from class
</select>
<resultMap id="selectAllClassRes" type="StuClass">
<id column="class_id" property="id"/>
<result column="class_name" property="name"/>
<collection property="students" column="class_id" select="com.haoyu.mapper.StudentMapper.selectAllStu"></collection>
</resultMap>
</mapper>
在StudentMapper.xml编写
<mapper namespace="com.haoyu.mapper.StudentMapper">
<select id="selectAllStu" resultMap="selectAllStuRes">
select * from student where class_id = #{class_id}
</select>
<resultMap id="selectAllStuRes" type="Student">
<id column="stu_id" property="id"/>
<result column="stu_name" property="name"/>
<result column="class_id" property="classId"/>
</resultMap>
</mapper>
7.3、模糊查询
编写接口方法
List<Student> selectStu(String str);
编写xml方法
<select id="selectStu" resultMap="selectAllStuRes">
select * from t_user where username like '%${username}%';
select * from student where stu_name like "%"#{str}"%"
select * from student where stu_name like concat('%',concat(#{str},'%'))
</select>
扩展:mybatis日期比较
编写接口方法
List<New> selectByDate(@Param("startTime") String startTime, @Param("endTime") String endTime);
xml语句格式
SELECT * FROM NEWS WHERE <![CDATA[ create_time >= #{startTime} AND create_time < #{endTime} ]]>
8、mybatis缓存
——缓存是指将数据保存在内存中,读取时无需再从磁盘读入,因此比较快。
——mybatis为减轻数据库压力,提高数据库性能,提供了两级缓存机制:
——https://blog.csdn.net/weixin_45486926/article/details/124883009
8.1、一级缓存
- mybatis默认开启,SqlSession级别的缓存(只在同一个SqlSession内有效)
- SqlSession一旦刷新或关闭,都会清除一级缓存。
- 底层基于HashMap
- 在操作数据库时需要构造sqlSession对象,在对象中有一个HashMap本地缓存数据结构,用于缓存数据。不同的SQL会话(sqlSession)之间的缓存数据区域(HashMap)是互不影响的。
工作流程:
-
第一次查询用户id为1的用户信息,先去缓存中查询是否有id为1的用户信息,如果没有,从数据库中查询用户信息。得到用户信息后再将用户信息储存到一级缓存中。
-
第二次查询用户id为1的用户信息,先去缓存中查询是否有id为1的用户信息,如缓存中有,直接从缓存中获取。
案例:
@Test public void test(){ SqlSession sqlSession = ssf.openSession(); IDeptMapper mapper = sqlSession.getMapper(IDeptMapper.class); //第一次查询id=10的数据 Dept dept = mapper.selectById(10); System.out.println(dept); //第二次查询id=10的数据 Dept dept2 = mapper.selectById(10); //不要求调用同一个方法,只要最终执行的SQL语句一致就会触发二级缓存 System.out.println(dept2); }
-
注意:两次查询须在同一个sqlsession中完成,否则将不会走mybatis的一级缓存。在mybatis与spring进行整合开发时,事务控制在service中进行,重复调用两次servcie将不会走一级缓存,因为在第二次调用时session方法结束,SqlSession就关闭了。
8.2、二级缓存
- 需要手动开启,mapper级别的缓存(同一个namespace公用这一个缓存),多个SQL会话(sqlSession)共享。
- 多个sqlSession去操作同一个Mapper的sql语句,多个sqlSession可以共用二级缓存,二级缓存是跨sqlSession的。
- 缓存机制与一级缓存一致。
工作流程:
- 当一个sqlseesion执行了一次select后,并关闭此session的时候,就会将查询结果存储到二级缓存中。
- 当另一个sqlsession执行相同select时,首先会查询二级缓存,二级缓存中无对应数据,再去查询一级缓存,一级缓存中也没有,最后去数据库查找,从而减少了数据库压力提高了性能。
开启步骤:
-
通过application.yml配置二级缓存开启
# mybatis相关配置 mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启MyBatis的二级缓存 cache-enabled: true
或通过MyBatis配置文件开启二级缓存
<setting name="cacheEnabled" value="true"/>
-
在 xxxMapper.xml 文件中添加
//添加在外面,全局开启 <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"/> //将标签属性useCache="true",对当前语句开启
-
在需要缓存的对象实现Serializable接口(注意,根据SQL语句查询字段所出现的对象都要继承接口),sqlSession关闭后才会缓存(便于一次性将所有的查询结果进行缓存)
案例:
@Test public void test2(){ //开启SQL会话 SqlSession sqlSession1 = ssf.openSession(); IDeptMapper mapper1 = sqlSession1.getMapper(IDeptMapper.class); Dept dept = mapper1.selectById(10); System.out.println(dept); sqlSession1.close(); //开启一个新的SQL会话 SqlSession sqlSession2 = ssf.openSession(); IDeptMapper mapper2 = sqlSession2.getMapper(IDeptMapper.class); Dept dept2 = mapper2.selectById(10); //在同一个方法内的多个sqlSession共享数据,并且必须调用同一个方法是二级缓存才有效。 System.out.println(dept2); sqlSession2.close(); }
8.3、注意事项
-
如果sqlSession执行了DML操作(insert、update、delete),并commit了,就会清空sqlSession中的一级缓存,保证缓存中始终保存的是最新的信息,避免脏读。
-
mybatis的缓存是基于 [ namespace:sql语句:参数 ] 来进行缓存的(SqlSession的HashMap存储缓存数据时,是使用
[ namespace:sql:参数 ] 作为key,查询返回的语句作为value保存的)。
工作流程:
- 当一个sqlseesion执行了一次select后,并关闭此session的时候,就会将查询结果存储到二级缓存中。
- 当另一个sqlsession执行相同select时,首先会查询二级缓存,二级缓存中无对应数据,再去查询一级缓存,一级缓存中也没有,最后去数据库查找,从而减少了数据库压力提高了性能。
开启步骤:
-
通过application.yml配置二级缓存开启
# mybatis相关配置 mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启MyBatis的二级缓存 cache-enabled: true
或通过MyBatis配置文件开启二级缓存
<setting name="cacheEnabled" value="true"/>
-
在 xxxMapper.xml 文件中添加
//添加在外面,全局开启 <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"/> //将标签属性useCache="true",对当前语句开启
-
在需要缓存的对象实现Serializable接口(注意,根据SQL语句查询字段所出现的对象都要继承接口),sqlSession关闭后才会缓存(便于一次性将所有的查询结果进行缓存)
案例:
@Test public void test2(){ //开启SQL会话 SqlSession sqlSession1 = ssf.openSession(); IDeptMapper mapper1 = sqlSession1.getMapper(IDeptMapper.class); Dept dept = mapper1.selectById(10); System.out.println(dept); sqlSession1.close(); //开启一个新的SQL会话 SqlSession sqlSession2 = ssf.openSession(); IDeptMapper mapper2 = sqlSession2.getMapper(IDeptMapper.class); Dept dept2 = mapper2.selectById(10); //在同一个方法内的多个sqlSession共享数据,并且必须调用同一个方法是二级缓存才有效。 System.out.println(dept2); sqlSession2.close(); }