Mybatis练习
- 能够使用映射配置文件实现CRUD操作
- 能够使用注解实现CRUD操作
如上图所示产品原型,里面包含了品牌数据的 查询 、 按条件查询 、 添加 、 删除 、 批量删除 、 修改 等功能,而这些功能其实就是对数据库表中的数据进行CRUD操作。接下来我们就使用Mybatis完成品牌数据的增删改查操作。以下是我们要完成功能列表
- 查询
- 查询所有数据
- 查询详情
- 条件查询
- 添加
- 修改
- 修改全部字段
- 修改动态字段
- 删除
- 删除一个
- 批量删除
一. 准备文件和类
案例结构图
数据库表的准备(tb_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),
('香飘飘', '香飘飘', 1000, '绕地球三圈', 0);
实体类 (Brand)
package com.sgs.pojo;
public class Brand {
// id 主键
private Integer id;
// 品牌名称
private String brandName;
// 企业名称
private String companyName;
// 排序字段
private String ordered;
// 描述信息
private String description;
// 状态:0:禁用 1:启用
private Integer status;
public Brand() {
}
public Brand(Integer id, String brandName, String companyName, String ordered, String description, Integer status) {
this.id = id;
this.brandName = brandName;
this.companyName = companyName;
this.ordered = ordered;
this.description = description;
this.status = status;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getbrandName() {
return brandName;
}
public void setbrandName(String brandName) {
this.brandName = brandName;
}
public String getcompanyName() {
return companyName;
}
public void setcompanyName(String companyName) {
this.companyName = companyName;
}
public String getOrdered() {
return ordered;
}
public void setOrdered(String ordered) {
this.ordered = ordered;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
@Override
public String toString() {
return "Brand{" +
"id=" + id +
", brandName='" + brandName + '\'' +
", companyName='" + companyName + '\'' +
", ordered='" + ordered + '\'' +
", description='" + description + '\'' +
", status=" + status +
'}';
}
}
编写测试用例
测试代码需要在 test/java 目录下创建包及测试用例。
//构建SqlSessionFactory
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过Mapper代理获取相对应的接口
SqlSession session = sqlSessionFactory.openSession();
/* */ mapper = session.getMapper(/* */);
pom.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>Mybatis-case</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!-- 添加slf4j日志api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</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>
</dependencies>
</project>
logback.xml日志文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%level] %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>
</encoder>
</appender>
<logger name="com.sgs" level="DEBUG" additivity="false">
<appender-ref ref="Console"/>
</logger>
<!--
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
, 默认debug
<root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
-->
<root level="DEBUG">
<appender-ref ref="Console"/>
</root>
</configuration>
mybatis.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 给resultType起别名-->
<typeAliases>
<package name="com.sgs.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 登录信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="JDBC:mysql://localhost:3306/db1?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="o676448"/>
</dataSource>
</environment>
</environments>
<!-- 映射器-->
<mappers>
<!-- 映射在文件中-->
<!-- <mapper resource="com/sgs/mapper/BrandMapper.xml"/>-->
<!-- 通过包扫描映射-->
<package name="com.sgs.mapper"/>
</mappers>
</configuration>
BrandMapper.xml映射器文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sgs.mapper.BrandMapper">
<!-- 数据库表的名称 和 实体类表的属性名称 不一样, 则不能封装数据-->
<resultMap id="BrandResultMap" type="Brand">
<result column="brand_name" property="brandName"/>
<result column="company_name" property="companyName"/>
</resultMap>
<!-- <select id="...." resultType="Brand">-->
<!-- .......... 后边详细如下 -->
<!-- </select>-->
<select id="....." resultMap="BrandResultMap">
.......... 后边详细如下
</select>
BrandMapper接口
package com.sgs.mapper;
import com.sgs.pojo.Brand;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface BrandMapper {
//查询所有
// List<Brand> selectAll();
//查询单个
//Brand selectById(int id);
//按条件查询
//按散装查询
// List<Brand> selectByCondition(@Param("status") int status,@Param("companyName") String companyName,@Param("brandName") String brandName);
//
// //按对象参数的查询
// List<Brand> selectByCondition1(Brand brand);
//
// //按Map查询
// List<Brand> selectByCondition1(Map map);
//按条件查询
// List<Brand> selectByConditionSingle(Map map);
// 后边详细介绍
}
安装 MyBatisX 插件
- MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。
- 主要功能
- XML映射配置文件 和 接口方法 间相互跳转
- 根据接口方法生成 statement
- 安装方式
- 点击 file ,选择 settings ,就能看到如下图所示界面(安装后重新启动IDEA)
- 点击 file ,选择 settings ,就能看到如下图所示界面(安装后重新启动IDEA)
- 红色头绳的表示映射配置文件,蓝色头绳的表示mapper接口。在mapper接口点击红色头绳的小鸟图标会自动跳转到对应的映射配置文件,在映射配置文件中点击蓝色头绳的小鸟图标会自动跳转到对应的mapper接口。也可以在mapper接口中定义方法,自动生成映射配置文件中的 statement ,如图所示
一. 查询
1.1 查询所有数据
分以下步骤进行实现:
- 编写接口方法:BrandMapper接口
- 参数:无
查询所有数据功能是不需要根据任何条件进行查询的,所以此方法不需要参数。
- 参数:无
public interface BrandMapper {
/**
* 查询所有
*/
List<Brand> selectAll();
}
- 结果:List
我们会将查询出来的每一条数据封装成一个 Brand 对象,而多条数据封装多个 Brand 对象,需要将这些对象封装到List集合中返回
<!-- 写法2 -->
<!-- 数据库表的名称 和 实体类表的属性名称 不一样, 则不能封装数据-->
<resultMap id="BrandResultMap" type="Brand">
<result column="brand_name" property="brandName"/>
<result column="company_name" property="companyName"/>
</resultMap>
<!-- 写法1 会出现变量映射失败问题!!!!-->
<!-- <select id="selectAll" resultType="Brand">-->
<!-- select * from tb_brand;-->
<!-- </select>-->
<!-- 写法2 -->
<select id="selectAll" resultMap="BrandResultMap">
select * from tb_brand;
</select>
<!-- 写法3 麻烦不灵活,但是也是要知道的-->
<sql id="brand_column">
id,brand_name as brandName,company_name as companyName,ordered,description,status
</sql>
<select id="selectAll" resultType="Brand">
select
<include refid="brand_column"/>
from tb_brand;
</select>
- 在 CaseDemo类中编写测试查询所有的方法
@Test
public void testSelect() throws Exception {
//构建SqlSessionFactory
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过Mapper代理获取相对应的接口
SqlSession session = sqlSessionFactory.openSession();
BrandMapper mapper = session.getMapper(BrandMapper.class);
//获取BrandMapper相对应的方法
List<Brand> brands = mapper.selectAll();
System.out.println(brands);
//释放资源
session.close();
}
注意:现在我们感觉测试这部分代码写起来特别麻烦,我们可以先忍忍。以后我们只会写上面的第3步的代码,其他的都不需要我们来完成
执行测试方法结果如下(写法2):
注意:有些数据封装成功了,而有些数据并没有封装成功的时候
写法1:
解决方法
- 给字段起别名
- 从上面结果可以看到 brandName 和 companyName 这两个属性的数据没有封装成功,查询 实体类和 表中的字段 发现,在实体类中属性名是 brandName 和 companyName ,而表中的字段名为 brand_name 和 company_name,如下图所示 。那么我们只需要保持这两部分的名称一致这个问题就迎刃而解。
- 使用resultMap定义字段和属性的映射关系
- id:完成主键字段的映射
column:表的列名
property:实体类的属性名- iresult:完成一般字段的映射
column:表的列名
property:实体类的属性名
1.2 查询详情
有些数据的属性比较多,在页面表格中无法全部实现,而只会显示部分,而其他属性数据的查询可以通过 查看详情 来进行查询,如上图所示。
查看详情功能实现步骤:
- 编写接口方法:Mapper接口
/**
* 查看详情:根据Id查询
*/
Brand selectById(int id);
- 参数:id
查看详情就是查询某一行数据,所以需要根据id进行查询。而id以后是由页面传递过来
结果:Brand
根据id查询出来的数据只要一条,而将一条数据封装成一个Brand对象即可
- 编写SQL语句:SQL映射文件BrandMapper.xml
<resultMap id="BrandResultMap" type="Brand">
<result column="brand_name" property="brandName"/>
<result column="company_name" property="companyName"/>
</resultMap>
<select id="selectById" parameterType="int" resultMap="BrandResultMap">
select *from tb_brand where id=#{id};
</select>
在 BrandMapper.xml 映射配置文件中编写 statement ,使用 resultMap 而不是使用 resultType
- 编写测试方法
在 test/java 下的 CaseDemo类中 定义测试方法
@Test
public void testSelectById() throws Exception {
//构建SqlSessionFactory
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过Mapper代理获取相对应的接口
SqlSession session = sqlSessionFactory.openSession();
BrandMapper mapper = session.getMapper(BrandMapper.class);
//获取BrandMapper相对应的方法
Brand brand = mapper.selectById(1);
System.out.println(brand);
//释放资源
session.close();
}
注意:上述SQL中的 #{id}先这样写,下面我们详细讲解
参数占位符:
查询到的结果很好理解就是id为1的这行数据。而这里我们需要看控制台显示的SQL语句,能看到使用?进行占位。说明我们在映射配置文件中的写的 #{id} 最终会被?进行占位。接下来我们就聊聊映射配置文件中的参数占位符。
mybatis提供了两种参数占位符:
#{}
:执行SQL时,会将 #{} 占位符替换为?,将来自动设置参数值。从上述例子可以看出使用#{} 底层使用的是PreparedStatement${}
:拼接SQL。底层使用的是 Statement ,会存在SQL注入问题。如下图将 映射配置文件中的 #{} 替换成 ${} 来看效果
<select id="selectById" parameterType="int" resultMap="BrandResultMap">
select *from tb_brand where id=${id};
</select>
重新运行查看结果如下:
注意:从上面两个例子可以看出,以后开发我们使用 #{}
参数占位符。
parameterType使用:
对于有参数的mapper接口方法,我们在映射配置文件中应该配置 ParameterType 来指定参数类型。只不过该属性都可以省略。如下图
SQL语句中特殊字段处理:
以后肯定会在SQL语句中写一下特殊字符,比如某一个字段大于某个值,可以看出报错了,因为映射配置文件是xml类型的问题:
而 > < 等这些字符在xml中有特殊含义,所以此时我们需要将这些符号进行转义,可以使用以下两种方式进行转义
转义字符
& l t; 就是 < 的转义字符CDATA区
1.3 多条件查询
我们经常会遇到如上图所示的多条件查询,将多条件查询的结果展示在下方的数据列表中。而我们做这个功能需要分析最终的SQL语句应该是什么样,思考两个问题
- 条件表达式
- 如何连接
条件字段 企业名称 和 品牌名称 需要进行模糊查询,所以条件应该是:
简单的分析后,我们来看功能实现的步骤:- 编写接口方法
而该功能有三个参数,我们就需要考虑定义接口时,参数应该如何定义。Mybatis针对多参数有多种实现
参数:所有查询条件
结果:List
- 使用
@Param("参数名称")
标记每一个参数,在映射配置文件中就需要使用#{参数名称}
进行占位 - 将多个参数封装成一个 实体对象 ,将该实体对象作为接口的方法参数。该方式要求在映射配置文件的SQL中使用
#{内容}
时,里面的内容必须和实体类属性名保持一致
。 - 将多个参数封装到map集合中,将map集合作为接口的方法参数。该方式要求在映射配置文件的SQL中使用
#{内容}
时,里面的内容必须和map集合中键的名称一致
。
- 使用
- 编写接口方法
//按条件查询
//按散装查询
List<Brand> selectByCondition(@Param("status") int status,@Param("companyName") String companyName,@Param("brandName") String brandName);
//按对象参数的查询
List<Brand> selectByCondition1(Brand brand);
//按Map查询
List<Brand> selectByCondition1(Map map);
- 在映射配置文件中编写SQL语句
注意:此语句有问题,动态SQL会详细说明
<select id="selectByCondition" resultMap="BrandResultMap">
select *
from tb_brand
where
status = #{status}
and company_name like #{companyName},
and brand_name like #{brandName};
</select>
- 编写测试方法并执行
@Test
public void selectByCondition() throws Exception {
int status = 1;
String companyName="鸡你太美有限公司";
String brandName="篮球射";
//构建SqlSessionFactory
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过Mapper代理获取相对应的接口
SqlSession session = sqlSessionFactory.openSession();
BrandMapper mapper = session.getMapper(BrandMapper.class);
companyName="%"+companyName+"%";
brandName="%"+brandName+"%";
//获取BrandMapper相对应的方法
//按散装查询
// List<Brand> brands = mapper.selectByCondition(status, companyName, brandName);
// System.out.println(brands);
/*
//按对象查询
Brand brand = new Brand();
brand.setStatus(1);
brand.setcompanyName(companyName);
brand.setbrandName(brandName);
List<Brand> brands = mapper.selectByCondition1(brand);
System.out.println(brands);
*/
//按Map查询
Map map = new HashMap();
map.put("status","status");
map.put("companyName",companyName);
map.put("brandName",brandName);
List<Brand> brands = mapper.selectByCondition1(map);
System.out.println(brands);
//释放资源
session.close();
}
注意:上述的问题仅限于所有条件都有情况下,但是,也会有条件不全的情况下的,下面我们讲解动态SQL来解决
动态SQL
针对上述的需要,Mybatis对动态SQL有很强大的支撑:
if
choose (when, otherwise)
trim (where, set)
foreach
我们先学习 if 标签和 where 标签:
- if 标签:条件判断
- test 属性:逻辑表达式
<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>
</select>
如上的这种SQL语句就会根据传递的参数值进行动态的拼接。如果此时status和companyName有值那么就会值拼接这两个条件。
- 而下面的语句中 where 关键后直接跟 and 关键字,这就是一条错误的SQL语句。这个就可以使用 where 标签解决
解决:
1 恒等式 where 1=1
<select id="selectByCondition1" resultMap="BrandResultMap">
select *
from tb_brand
where 1=1
<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>
</select>
2 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>
单个条件(动态SQL)
如上图所示,在查询时只能选择
品牌名称 、 当前状态 、 企业名称 这三个条件中的一个,但是用户到底选择哪儿一个,我们并不能确定。这种就属于单个条件的动态SQL语句
这种需求需要使用到 choose(when,otherwise)标签
实现, 而 choose 标签类似于Java 中的switch语句。
- BrandMapper接口
public interface BrandMapper {
//按条件查询
List<Brand> selectByConditionSingle(Map map);
}
- CaseDemo测试类
@Test
public void selectByConditionSingle() throws Exception {
Integer status = 0;
String companyName=null;
String brandName=null;
//构建SqlSessionFactory
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过Mapper代理获取相对应的接口
SqlSession session = sqlSessionFactory.openSession();
BrandMapper mapper = session.getMapper(BrandMapper.class);
// companyName="%"+companyName+"%";
// brandName="%"+brandName+"%";
//获取BrandMapper相对应的方法
//按Map查询
Map map = new HashMap();
map.put("status",status);
// map.put("companyName",companyName);
// map.put("brandName",brandName);
List<Brand> brands = mapper.selectByConditionSingle(map);
System.out.println(brands);
//释放资源
session.close();
}
- BrandMapper.xml映射文件下
<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>
执行的内容: