MyBatis入门
什么是MyBatis?
MyBatis是一款优秀的持久层框架,用于简化JDBC开发
MyBatis 本是 Apache 的一个开源项目 iBatis,2010年这个项目由 apache software foundation 迁移到了 google code,并且改名为 MyBatis,2013年11月迁移到Github
持久层
负责将数据保持到数据库的那一层代码
JavaEE三层架构:表现层、业务层、持久层
框架
框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型
在框架的基础之上构建软件编写更加高效、规范、通用、可扩展
JDBC缺点
硬编码
- 注册驱动,获取连接
- SQL语句
操作繁琐
- 手动设置参数
- 手动封装结果集
MyBatis简化
- 将硬编码抽取到配置文件当中
- 操作繁琐的部分自动完成
- 免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作
MyBatis快速入门
查询user表中所有数据
- 创建user表,添加数据
create database mybatis;
use mybatis;
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', '男', '西安');
- 创建模块,导入坐标
<dependencies>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.16</version>
</dependency>
<!-- mysql 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!--junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- 添加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>
</dependencies>
注意:需要在项目的resources目录下创建logback.xml的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%level] %blue(%d{HH:mm:ss.SSS}) %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>
</encoder>
</appender>
<logger name="com.ling" 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核心配置文件 --> 替换连接信息,解决硬编码问题
- 在模块下的resources目录下创建mybatis-config.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>
<!--
environments: 配置数据库连接环境信息,可以配置多个environment,通过default属性切换不同的environment
-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--加载sql映射文件-->
<mapper resource="UserMapper.xml"/>
<!--Mapper代理方式-->
<!-- <package name="com.ling.mapper"/>-->
</mappers>
</configuration>
- 编写SQL映射文件 --> 统一管理sql语句,解决硬编码问题
- 在模块的resorces目录下创建映射配置文件UserMapping.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="test">
<select id="selectAll" resultType="com.itheima.pojo.User">
select * from tb_user;
</select>
</mapper>
- 编码
- 定义POJO类
-
package com.ling.pojo; public class User { private Integer id; private String username; private String password; private String gender; private String addr; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String getAddr() { return addr; } public void setAddr(String addr) { this.addr = addr; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", gender='" + gender + '\'' + ", addr='" + addr + '\'' + '}'; } }
- 加载核心配置文件,获取SqlSessionFactory对象
-
// 1.加载mybatis核心配置文件,获取SqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
- 获取SqlSession对象,执行SQL语句
-
// 2.获取SqlSession对象,可以用它来执行sql SqlSession sqlSession = sqlSessionFactory.openSession(); // 3.执行sql List<Object> users = sqlSession.selectList("test.selectAll");
- 释放资源
-
// 4.释放资源 sqlSession.close();
Mapper代理开发
目的
- 解决原生方式中的硬编码
- 简化后期执行SQL
Mapper代理开发概述
之前我们写的代码是基本使用方式,它也存在硬编码的问题,如下:
// 3.执行sql
List<Object> users = sqlSession.selectList("test.selectAll");
System.out.println(users);
这里调用 selectList()
方法传递的参数是映射配置文件中的 namespace.id值。这样写也不便于后期的维护。如果使用 Mapper 代理方式(如下图)则不存在硬编码问题。
// 3.1 使用mapper代理方法
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectAll();
通过上面的描述可以看出 Mapper 代理方式的目的:
- 解决原生方式中的硬编码
- 简化后期执行SQL
Mybatis 官网也是推荐使用 Mapper 代理的方式。下图是截止官网的图片
使用Mapper代理方式完成入门案例
- 定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下
- 设置SQL映射文件的namespace属性为Mapper接口的全限定名
- 在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致
- 编码
- 在
com.ling.mapper
包下创建 UserMapper接口,代码如下: -
package com.ling.mapper; import com.ling.pojo.User; import java.util.List; public interface UserMapper { List<User> selectAll(); }
- 在
resources
下创建com/ling/mapper
目录,并在该目录下创建 UserMapper.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"> <!-- namespace: 名称空间 --> <mapper namespace="com.ling.mapper.UserMapper"> <select id="selectAll" resultType="com.ling.pojo.User"> select * from tb_user; </select> </mapper>
- 通过SqlSession的getMapper方法获取Mapper接口的代理对象
- 调用对应方法完成sql的执行
- 在
package com.ling;
import com.ling.mapper.UserMapper;
import com.ling.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/*
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
// List<Object> users = sqlSession.selectList("test.selectAll");
// 3.1 使用mapper代理方法
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectAll();
System.out.println(users);
// 4.释放资源
sqlSession.close();
}
}
细节:如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载
核心配置文件
核心配置文件中现有的配置之前已经给大家进行了解释,而核心配置文件中还可以配置很多内容。我们可以通过查询官网看可以配置的内容
接下来我们先对里面的一些配置进行讲解。
多环境配置
在核心配置文件的 environments
标签中其实是可以配置多个 environment
,使用 id
给每段环境起名,在 environments
中使用 default='环境id'
来指定使用哪段配置。我们一般就配置一个 environment
即可。
<!--
environments: 配置数据库连接环境信息,可以配置多个environment,通过default属性切换不同的environment
-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
类型别名
在映射文件中的resultType属性需要配置数据封装的类型(类的全限定名),而每次这样写是特别麻烦的,Mybatis提供了类型别名(typeAliaser)可以简化这部分的书写。
首先需要在核心配置文件中配置类型别名,也就意味着给pojo包下的所有的类起了别名(别名就是类名),不区分大小写。内容如下:
<typeAliases>
<!--name属性的值是实体类所在包-->
<package name="com.ling.pojo"/>
</typeAliases>
通过上述的配置,我们就可以简化映射配置文件中 resultType
属性值的编写
<mapper namespace="com.ling.mapper.UserMapper">
<select id="selectAll" resultType="User">
select * from tb_user;
</select>
</mapper>
配置文件完成增删改查
准备环境
数据库表 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);
SELECT * FROM tb_brand;
实体类 Brand
package com.ling.pojo;
/**
* 品牌
*
* alt + 鼠标左键:整列编辑
*
* 在实体类中,基本数据类型建议使用其对应的包装类型
*/
public class Brand {
// id 主键
private Integer id;
// 品牌名称
private String brandName;
// 企业名称
private String companyName;
// 排序字段
private Integer ordered;
// 描述信息
private String description;
// 状态:0:禁用 1:启用
private Integer 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 Integer getOrdered() {
return ordered;
}
public void setOrdered(Integer 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 +
'}';
}
}
测试用例
安装 MyBatisX 插件
MyBatisX 是一款基于IDEA的快读开发插件,为效率而生
主要功能:
- XML和接口方法相互跳转
- 模糊接口方法生成statement
安装
查询——查询所有数据
编写接口方法
package com.ling.mapper;
import com.ling.pojo.Brand;
import java.util.List;
public interface BrandMapper {
/*
查询所有
*/
List<Brand> selectAll();
}
编写SQL语句:SQL映射文件
<?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">
<!--
namespace: 名称空间
-->
<mapper namespace="com.ling.mapper.BrandMapper">
<!--
数据库表的字段名称 和 实体类的属性名称不一样,就不能自动封装数据
1.起别名: 对不一样的列名起别名,让别名和实体类的属性名一样
缺点:每次查询都需要定义一次别名
sql片段
缺点:不灵活
2.使用resultMap
定义resultMap标签
在 <select> 标签中,使用resultMap属性替换resultType属性
-->
<!--
id: 唯一标识
type: 返回值类型
-->
<resultMap id="brandResultMap" type="brand">
<!--
id:完成主键字段的映射
column:表的列名
property:实体类的属性名
result:完成一般字段的映射
column:表的列名
property:实体类的属性名
-->
<result column="brand_name" property="brandName"/>
<result column="company_name" property="companyName"/>
</resultMap>
<select id="selectAll" resultMap="brandResultMap">
select
*
from tb_brand;
</select>
<!--
sql片段:可重用的sql语句
-->
<!-- <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>-->
<!-- <select id="selectAll" resultType="Brand">-->
<!-- select * from tb_brand;-->
<!-- </select>-->
</mapper>
执行方法,测试
package com.ing.test;
import com.ling.mapper.BrandMapper;
import com.ling.pojo.Brand;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyBatisTest {
@Test
public void testSelectAll() throws IOException {
// 1. 获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 获取Mapper接口的代理对象
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
// 4. 执行方法
List<Brand> brands = mapper.selectAll();
// 5. 处理结果
System.out.println(brands);
// 6. 释放资源
sqlSession.close();
}
}
实体类属性名和数据库表列名不一致,不能自动封装数据
起别名:
- 在SQL语句中,对不一样的列名起别名,别名和实体类属性名一样
- 可以定义<sql>片段,提升复用性
resultMap:
- 定义<resultMap>完成不一致的属性名和列名的映射
查看详情
编写接口方法
Brand selectById(int id);
编写SQL语句:SQL映射文件
<!--
参数占位符:
#{}:相当于PreparedStatement中的?,防止SQL注入
${}:字符串拼接,会直接把参数进行拼接,存在SQL注入问题
使用时机:
参数传递的时候:#{}
表名或者列名不固定的时候:${}
参数类型:parameterType 可以省略
特殊字符类型:
1、转义字符:比如<、>、&等,可以使用转义字符 <、>、&
2、CDATA区:把特殊字符当成普通字符处理 <![CDATA[<]]>
-->
<select id="selectById" parameterType="int" resultMap="brandResultMap">
select * from tb_brand where id = #{id};
</select>
执行方法,测试
@Test
public void testSelectById() throws IOException {
// 接收参数
int id = 1;
// 1. 获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 获取Mapper接口的代理对象
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
// 4. 执行方法
Brand brand = mapper.selectById(id);
// 5. 处理结果
System.out.println(brand);
// 6. 释放资源
sqlSession.close();
}
参数占位符
- #{}:执行SQL时,会将#{}字符替换成?,将来自动设置参数值
- ${}:拼SQL,会存在SQL注入问题
- 使用时机:
- 参数传递都是用#{}
- 如果要对表名、列名进行动态设置,只能使用${}进行sql拼接
parameterType
- 用于设置参数类型,该参数可以省略
SQL语句中特殊字符处理
- 转义字符
- <![CDATA[内容]]>
条件查询
多条件查询
编写接口方法
/**
* 条件查询
* 参数接收
* 1、散装参数:如果方法中有多个参数,需要使用@Param("SQL参数占位符名称")
* 2、对象参数: 对象的属性的名称要和参数占位符名称一致
* 3、map集合参数
* @param status
* @param companyName
* @param brandName
* @return
*/
List<Brand> selectByCondition(@Param("status") int status, @Param("companyName") String companyName, @Param("brandName") String brandName);
List<Brand> selectByCondition(Brand brand);
List<Brand> selectByCondition(Map map);
编写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 testSelectByCondition() throws IOException {
// 接收参数
int status = 1;
String companyName = "华为";
String brandName = "华为";
// 处理参数
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";
// 封装对象
/*Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);*/
Map map = new HashMap();
map.put("status", status);
map.put("companyName", companyName);
map.put("brandName", brandName);
// 1. 获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 获取Mapper接口的代理对象
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
// 4. 执行方法
// List<Brand> brands = mapper.selectByCondition(status, companyName, brandName);
// List<Brand> brands = mapper.selectByCondition(brand);
List<Brand> brands = mapper.selectByCondition(map);
// 5. 处理结果
System.out.println(brands);
// 6. 释放资源
sqlSession.close();
}
SQL语句设置多个参数有几种方式
散装参数:
- 需要使用@Param("SQL中的参数占位符名称")
实体类封装参数
- 只需要保证SQL中的参数名 和 实体类属性名对应上,即可设置成功
map集合
- 只需要保证SQL中的参数名 和 map集合的键的名称对应上,即可设置成功
多条件的动态条件查询
编写接口方法
在 BrandMapper 接口中定义多条件查询的方法。
而该功能有三个参数,我们就需要考虑定义接口时,参数应该如何定义。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);
/**
* 条件查询
* 参数接收
* 1、散装参数:如果方法中有多个参数,需要使用@Param("SQL参数占位符名称")
* 2、对象参数: 对象的属性的名称要和参数占位符名称一致
* 3、map集合参数
* @param status
* @param companyName
* @param brandName
* @return
*/
List<Brand> selectByCondition(@Param("status") int status, @Param("companyName") String companyName, @Param("brandName") String brandName);
List<Brand> selectByCondition(Brand brand);
List<Brand> selectByCondition(Map map);
编写SQL语句
<!--
动态条件查询
if: 条件判断
test: 判断表达式,OGNL表达式
问题:
恒等式
<where>替换 where 关键字
-->
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
<!--where 1 = 1-->
where
<if test="status != null">
and status = #{status}
</if>
<if test="companyName != null and companyName != ''">
and company_name like #{companyName}and company_name like #{companyName}
</if>
<if test="brandName != null and brandName != ''">
and brand_name like #{brandName}
</if>
</where>
</select>
但是它也存在问题,如果此时给的参数值是
Map map = new HashMap();
// map.put("status" , status);
map.put("companyName", companyName);
map.put("brandName" , brandName);
拼接的SQL语句就变成了
select * from tb_brand where and company_name like ? and brand_name like ?
而上面的语句中 where 关键后直接跟 and 关键字,这就是一条错误的SQL语句。这个就可以使用 <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>
注意:需要给每个条件前都加上 and 关键字。
执行方法,测试
@Test
public void testSelectByCondition() throws IOException {
// 接收参数
int status = 1;
String companyName = "华为";
String brandName = "华为";
// 处理参数
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";
// 封装对象
/*Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);*/
Map map = new HashMap();
map.put("status", status);
map.put("companyName", companyName);
map.put("brandName", brandName);
// 1. 获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 获取Mapper接口的代理对象
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
// 4. 执行方法
// List<Brand> brands = mapper.selectByCondition(status, companyName, brandName);
// List<Brand> brands = mapper.selectByCondition(brand);
List<Brand> brands = mapper.selectByCondition(map);
// 5. 处理结果
System.out.println(brands);
// 6. 释放资源
sqlSession.close();
}
注意这里的模糊查询两边加上%,但是这个%不能在映射文件中的#{}中去加,而是只能在传入形参之前对数据进行处理,再进行传入操作。
如上的程序有一个小bug,那就是如果用户没有填写所有的搜索条件,那么结果是查询不到的。
动态SQL
if:用于判断参数是否优质,使用test属性进行条件判断
- 存在的问题:第一个条件不需要逻辑运算符
- 解决方案
- 使用恒等式让所有条件的格式都一样 where 1 = 1 (and ...)
- <where>标签替换where关键字
- 替换where关键字
- 会动态的去掉第一个条件前的 and
- 如果所有的参数没有值则不加where关键字
单条件的动态条件查询
编写接口方法
/**
* 单条件动态查询
* @param brand
* @return
*/
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 != ''">
company_name like #{companyName}
</when>
<when test="brandName != null and brandName != ''">
brand_name like #{brandName}
</when>
</choose>
</where>
</select>
注意这个地方使用<where>是为了处理用户不进行选择就进行查询的情况。这种情况同样有第二种解决办法:恒等式
执行方法,测试
@Test
public void testSelectByConditionSingle() throws IOException {
// 接收参数
int status = 1;
String companyName = "华为";
String brandName = "华为";
// 处理参数
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";
// 封装对象
Brand brand = new Brand();
// brand.setStatus(status);
brand.setCompanyName(companyName);
// brand.setBrandName(brandName);
/*Map map = new HashMap();
map.put("status", status);
map.put("companyName", companyName);
map.put("brandName", brandName);*/
// 1. 获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 获取Mapper接口的代理对象
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
// 4. 执行方法
List<Brand> brands = mapper.selectByCondition(status, companyName, brandName);
List<Brand> brands = mapper.selectByCondition(brand);
List<Brand> brands = mapper.selectByCondition(map);
List<Brand> brands = mapper.selectByConditionSingle(brand);
// 5. 处理结果
System.out.println(brands);
// 6. 释放资源
sqlSession.close();
}
动态SQL
choose(when,otherwise)
- 存在的问题:如果多个<when>标签的判断都不满足,就会报错
- 解决方案:
- 添加<otherwise>里面的内容,一般写作恒等式
- 将where关键字改成<where>标签
添加
编写接口方法
由于添加操作不需要返回值,所以返回值类型写void即可
/**
* 添加
* @param brand
*/
void add(Brand brand);
编写SQL语句
这里就不需要调用之前写的resultMap的内容了
<insert id="add">
insert into tb_brand (brand_name, company_name, ordered, description, status)
values (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status})
</insert>
编写测试方法
@Test
public void testAdd() throws IOException {
// 接收参数
int status = 1;
String companyName = "波导手机";
String brandName = "波导";
String description = "手机中的战斗机";
int ordered = 100;
// 封装对象
Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);
brand.setDescription(description);
brand.setOrdered(ordered);
// 1. 获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 获取Mapper接口的代理对象
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
// 4. 执行方法
mapper.add(brand);
// 5. 提交事务
sqlSession.commit();
// 6. 释放资源
sqlSession.close();
}
注意:
使用openSession是默认开启事务的,如果不提交事务的话,数据库不会发生变化。在这里有两种解决办法:
- openSession():默认开启事务,但是再进行增删改操作后需要手动提交事务,使用sqlSession.commit(); 手动提交事务
- openSession(true):可以设置为自动提交事务(关闭事务)
添加——主键返回
有时候我们需要返回添加数据的主键
比如:添加订单和订单项
- 添加订单
- 添加订单项,订单项中需要设置所属订单的id
做法:
在 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>
再直接使用对象的get方法就可以获得当前的主键值了
@Test
public void testAdd2() throws IOException {
// 接收参数
int status = 1;
String companyName = "波导手机";
String brandName = "波导";
String description = "手机中的战斗机";
int ordered = 100;
// 封装对象
Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);
brand.setDescription(description);
brand.setOrdered(ordered);
// 1. 获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// SqlSession sqlSession = sqlSessionFactory.openSession(true);
// 3. 获取Mapper接口的代理对象
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
// 4. 执行方法
mapper.add(brand);
Integer id = brand.getId();
System.out.println(id);
// 5. 提交事务
sqlSession.commit();
// 6. 释放资源
sqlSession.close();
}
修改——修改全部字段
编写接口方法
/**
* 修改
* @param brand
* @return
*/
int update(Brand brand);
编写SQL语句
<update id="update">
update tb_brand
set
brand_name = #{brandName},
company_name = #{companyName},
ordered = #{ordered},
description = #{description},
status = #{status}
where id = #{id}
</update>
执行方法,测试
@Test
public void testUpdate() throws IOException {
// 接收参数
int status = 1;
String companyName = "波导手机";
String brandName = "波导";
String description = "波导手机,手机中的战斗机";
int ordered = 200;
int id = 5;
// 封装对象
Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);
brand.setDescription(description);
brand.setOrdered(ordered);
brand.setId(id);
// 1. 获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// SqlSession sqlSession = sqlSessionFactory.openSession(true);
// 3. 获取Mapper接口的代理对象
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
// 4. 执行方法
int rows = mapper.update(brand);
System.out.println("受影响的行数:" + rows);
// 5. 提交事务
sqlSession.commit();
// 6. 释放资源
sqlSession.close();
}
修改动态字段
假设一个场景,我们仅仅只需要修改某个字段,但是在这个条件下,其他没有修改的属性就会被设置成null。
我们的基本思路就是用if标签去包含每一项
这个地方我们又会遇到前面遇到过问题,就是如果用户什么都没有修改,那么只有一个set在那里肯定会报错。或者用户修改的其中一项而尾部有一个逗号,那么也会报错。这个时候我们就需要用到MyBatis给我们提供的set标签
set 标签可以用于动态包含需要更新的列,忽略其它不更新的列。
我们可以将sql语句改写为:
<update id="update">
update tb_brand
<set>
<if test="brandName != null and brandName != '' ">
brand_name = #{brandName},
</if>
<if test="companyName != null and companyName != '' ">
company_name = #{companyName},
</if>
<if test="ordered != null and ordered != '' ">
ordered = #{ordered},
</if>
<if test="description != null and description != '' ">
description = #{description},
</if>
<if test="status != null">
status = #{status}
</if>
</set>
where id = #{id}
</update>
删除一个
编写接口方法
/**
* 根据id删除
* @param id
*/
void deleteById(int id);
编写SQL方法
<delete id="deleteById">
delete from tb_brand where id = #{id}
</delete>
执行方法,测试
@Test
public void testDelete() throws IOException {
// 接收参数
int id = 5;
// 1. 获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession = sqlSessionFactory.openSession(true);
// 3. 获取Mapper接口的代理对象
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
// 4. 执行方法
mapper.deleteById(id);
// 5. 提交事务
sqlSession.commit();
// 6. 释放资源
sqlSession.close();
}
批量删除
编写接口方法
/**
* 批量删除
* @param ids
*/
void delete(int[] ids);
编写SQL方法
批量删除我们不知道要删除几个,所以编写SQL时需要遍历数组来拼接SQL语句。Mybatis 提供了 foreach 标签供我们使用。
foreach 标签
用来迭代任何可迭代的对象(如数组,集合)。
- collection 属性:要遍历的可迭代对象
- mybatis会将数组参数,封装为一个Map集合。
- 默认:array = 数组
- 使用@Param注解改变map集合的默认key的名称
-
/** * 批量删除 * @param ids */ void delete(@Param("ids") int[] ids);
<delete id="delete"> delete from tb_brand where id in <foreach collection="ids" item="id" separator="," open="(" close=")" > #{id} </foreach> </delete>
-
- item 属性:本次迭代获取到的元素。
- separator 属性:集合项迭代之间的分隔符。foreach 标签不会错误地添加多余的分隔符。也就是最后一次迭代不会加分隔符。
- open 属性:该属性值是在拼接SQL语句之前拼接的语句,只会拼接一次
- close 属性:该属性值是在拼接SQL语句拼接后拼接的语句,只会拼接一次
<delete id="delete">
delete from tb_brand
where id in
<foreach collection="array" item="id" separator="," open="(" close=")" >
#{id}
</foreach>
</delete>
假如数组中的id数据是{1,2,3},那么拼接后的sql语句就是:
delete from tb_brand where id in (1,2,3);
编写测试方法
@Test
public void testDeleteByIds() throws IOException {
// 接收参数
int[] ids = {3, 5, 6};
// 1. 获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// SqlSession sqlSession = sqlSessionFactory.openSession(true);
// 3. 获取Mapper接口的代理对象
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
// 4. 执行方法
mapper.delete(ids);
// 5. 提交事务
sqlSession.commit();
// 6. 释放资源
sqlSession.close();
}
MyBatis参数传递
MyBatis中的参数传递
MyBatis接口方法中可以接受各种各样的参数,如下:
- 单个参数
- POJO类型
- Map集合类型
- Collection集合嘞
- List集合类型
- Array类型
- 其他类型
- 多个参数
多个参数
如下面的代码,就是接收两个参数,而接收多个参数需要使用 @Param
注解,那么为什么要加该注解呢?这个问题要弄明白就必须来研究Mybatis 底层对于这些参数是如何处理的。
User select(@Param("username") String username,@Param("password") String password);
<select id="select" resultType="user">
select *
from tb_user
where
username=#{username}
and password=#{password}
</select>
我们在接口方法中定义多个参数,Mybatis 会将这些参数封装成 Map 集合对象,值就是参数值,而键在没有使用 @Param
注解时有以下命名规则:
- 以 arg 开头 :第一个参数就叫 arg0,第二个参数就叫 arg1,以此类推。如:
- map.put(“arg0”,参数值1);
- map.put(“arg1”,参数值2);
- 以 param 开头 : 第一个参数就叫 param1,第二个参数就叫 param2,依次类推。如:
- map.put(“param1”,参数值1);
- map.put(“param2”,参数值2);
代码验证
在UserMapper接口中定义如下方法
User select(String username,String password);
在UserMapper.xml映射配置文件中定义SQL
<select id="select" resultType="user">
select *
from tb_user
where
username=#{arg0}
and password=#{arg1}
</select>
或者
<select id="select" resultType="user">
select *
from tb_user
where
username=#{param1}
and password=#{aram2}
</select>
运行代码结果如下
但是,在映射配合文件的SQL语句中使用用 arg
开头的和 param
书写,代码的可读性会变的特别差,此时可以使用 @Param
注解。
在接口方法参数上使用 @Param
注解,MyBatis 会将 arg
开头的键名替换为对应注解的属性值。
代码验证
在UserMapper接口中定义如下方法,在user那么参数前加上@param注解
User select(@Param("username") String username, String password);
Mybatis 在封装 Map 集合时,键名就会变成如下:
map.put(“username”,参数值1);
map.put(“arg1”,参数值2);
map.put(“param1”,参数值1);
map.put(“param2”,参数值2);
在 UserMapper.xml
映射配置文件中定义SQL
<select id="select" resultType="user">
select *
from tb_user
where
username=#{username}
and password=#{param2}
</select>
运行程序结果没有报错。而如果将 #{}
中的 username
还是写成 arg0
<select id="select" resultType="user">
select *
from tb_user
where
username=#{arg0}
and password=#{param2}
</select>
-
运行程序则可以看到错误
结论:以后接口参数是多个时,在每个参数上都使用 @Param
注解。这样代码的可读性更高。
单个参数
- POJO 类型
- 直接使用。要求 属性名 和 参数占位符名称 一致
- Map 集合类型
- 直接使用。要求 map集合的键名 和 参数占位符名称 一致
- Collection 集合类型
- Mybatis 会将集合封装到 map 集合中,如下:
map.put(“arg0”,collection集合);
map.put(“collection”,collection集合;
可以使用 @Param 注解替换map集合中默认的 arg 键名。
- List 集合类型
- Mybatis 会将集合封装到 map 集合中,如下:
map.put(“arg0”,list集合);
map.put(“collection”,list集合);
map.put(“list”,list集合);
可以使用 @Param 注解替换map集合中默认的 arg 键名。
- Array 类型
- Mybatis 会将集合封装到 map 集合中,如下:
map.put(“arg0”,数组);
map.put(“array”,数组);
可以使用 @Param 注解替换map集合中默认的 arg 键名。
- 其他类型
- 比如int类型,参数占位符名称 叫什么都可以。尽量做到见名知意
注解完成增删改查
使用注解开发会比配置文件开发更加方便。如下就是使用注解进行开发
@Select(value = "select * from tb_user where id = #{id}")
public User select(int id);
注意:
- 注解是用来替换映射配置文件方式配置的,所以使用了注解,就不需要再映射配置文件中书写对应的
statement
Mybatis 针对 CURD 操作都提供了对应的注解,已经做到见名知意。如下:
- 查询 :@Select
- 添加 :@Insert
- 修改 :@Update
- 删除 :@Delete
接下来我们做一个案例来使用 Mybatis 的注解开发
代码实现
- 将之前案例中
UserMapper.xml
中的 根据id查询数据 的statement
注释掉
- 在
UserMapper
接口的selectById
方法上添加注解
- 运行测试程序也能正常查询到数据
注意:在官方文档中 入门
中有这样的一段话:
而我们之前写的动态 SQL 就是复杂的功能,如果用注解使用的话,就需要使用到 Mybatis 提供的SQL构建器来完成,而对应的代码如下:
上述代码将java代码和SQL语句融到了一块,使得代码的可读性大幅度降低。
参数映射
对于以下一个简单的select语句:
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
我们可以发现一个参数符号:
#{id}
这就告诉 MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样:
// 近似的 JDBC 代码,非 MyBatis 代码...
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);
上面的这个示例说明了一个非常简单的命名参数映射。鉴于参数类型(parameterType)会被自动设置为 int,这个参数可以随意命名。原始类型或简单数据类型(比如 Integer 和 String)因为没有其它属性,会用它们的值来作为参数。 然而,如果传入一个复杂的对象,行为就会有点不一样了。比如:
<insert id="insertUser" parameterType="User">
insert into users (id, username, password)
values (#{id}, #{username}, #{password})
</insert>
如果 User 类型的参数对象传递到了语句中,会查找 id、username 和 password 属性,然后将它们的值传入预处理语句的参数中。
对传递语句参数来说,这种方式真是干脆利落。
字符串替换
默认情况下,使用 #{} 参数语法时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以:
ORDER BY ${columnName}
这样,MyBatis 就不会修改或转义该字符串了。
当 SQL 语句中的元数据(如表名或列名)是动态生成的时候,字符串替换将会非常有用 。 举个例子,如果你想 select 一个表任意一列的数据时,不需要这样写:
@Select("select * from user where id = #{id}")
User findById(@Param("id") long id);
@Select("select * from user where name = #{name}")
User findByName(@Param("name") String name);
@Select("select * from user where email = #{email}")
User findByEmail(@Param("email") String email);
// 其它的 "findByXxx" 方法
而是可以只写这样一个方法:
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
因为#{}其本质也是赋值替换预处理语句中相应占位符的操作,所以这个地方你传入的id是string类型也可以
其中 ${column} 会被直接替换,而 #{value} 会使用 ? 预处理。 这样,就能完成同样的任务:
User userOfId1 = userMapper.findByColumn("id", 1L);
User userOfNameKid = userMapper.findByColumn("name", "kid");
User userOfEmail = userMapper.findByColumn("email", "noone@nowhere.com");
这种方式也同样适用于替换表名的情况。
提示 :用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击。因此,要么不允许用户输入这些字段,要么自行转义并检验这些参数。