SpringBoot 数据访问(MyBatis)
向 SQL 语句传参
#{} 形式
#{}
:如果传过来的值是字符串类型。那两边会自动加上 单引号。当传递给#{}
的参数值是非字符串类型(如整数、浮点数、布尔值等),MyBatis 不会为这些值添加引号。
#{ key }原理: 占位符 + 赋值 先 emp_id = ?, 然后 ? = 赋值
${} 形式
${}
:它会直接将传过来变量的值替换到 SQL 语句中。
${ key }: 字符串拼接 " emp_id = " + id
通常不会采用
${}
的方式传值。一个特定的适用场景是:通过Java程序动态生成数据库表,表名部分需要Java程序通过参数传入;而JDBC对于表名部分是不能使用问号占位符的,此时只能使用${}
.
结论:动态的是值就是用#{}
,动态变化的部分是诸如容器名、标签、列名、SQL 关键字等非普通值的情况时,则需要使用${}
进行拼接
传入单个简单类型参数
单个简单类型参数,在
#{}
中可以随意命名,但是没有必要。通常还是使用和接口方法参数同名。
Mapper 接口中抽象方法的声明
Employee selectEmployee(Integer empId);
SQL语句
<select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee">
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{empId}
</select>
传入实体类型参数
若
mapper
接口中的方法参数为实体类对象时,此时可以使用${}
和#{}
,通过访问实体类对象中的 属性名 获取属性值,注意${}
需要手动加单引号
.
原理:Mybatis会根据#{}
中传入的数据,加工成getXxx()
方法【pojo 类中必须有 get 方法】,通过反射在实体类对象中调用这个get
方法,从而获取到对应的数据。填充到#{}
解析后的问号占位符这个位置。
pojo
public class Employee {
private Integer empId;
private String empName;
private Double empSalary;
... get|set
}
Mapper 接口中抽象方法的声明
int insertEmployee(Employee employee);
SQL语句
<insert id="insertEmployee">
insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary})
</insert>
传入多个简单类型参数
方案1:注解指定 @Param 注解 指定多个简单参数的 key。
key = @Param("value值")
可以理解成别名 [推荐]
.
方案2:mybatis 默认机制:
- arg0 arg1… 形参从左到右依次对应 (name, salary) 【
name-> key = arg0
】 【salary -> key = arg1
】- param1 param2… (name, salary) 【
name-> key = param1
】 【salary -> key = param2
】
Mapper 接口中抽象方法的声明
int updateEmployee(@Param("empId") Integer empId,@Param("empSalary") Double empSalary);
SQL 语句
<update id="updateEmployee">
update t_emp set emp_salary=#{empSalary} where emp_id=#{empId}
</update>
传入 Map 类型参数
有很多零散的参数需要传递,但是没有对应的实体类类型可以使用。使用
@Param
注解一个一个传入又太麻烦了。所以都封装到Map中。
.
使用 map:手动创建map
集合,将这些数据放在map
中,只需要通过${}
和#{}
访问map
集合的键就可以获取相对应的值,
Mapper 接口中抽象方法的声明
int updateEmployeeByMap(Map<String, Object> paramMap);
SQL语句
<update id="updateEmployeeByMap">
update t_emp set emp_salary=#{empSalaryKey} where emp_id=#{empIdKey}
</update>
测试类
private SqlSession session;
//junit5会在每一个@Test方法前执行@BeforeEach方法
@BeforeEach
public void init() throws IOException {
session = new SqlSessionFactoryBuilder()
.build(
Resources.getResourceAsStream("mybatis-config.xml"))
.openSession();
}
@Test
public void testUpdateEmpNameByMap() {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
Map<String, Object> paramMap = new HashMap<>();
//设置要传入的 map 的 key 和 value
paramMap.put("empSalaryKey", 999.99);
paramMap.put("empIdKey", 5);
//把 map 传递进去
int result = mapper.updateEmployeeByMap(paramMap);
System.out.println("result = " + result);
}
//junit5会在每一个@Test方法后执行@@AfterEach方法
@AfterEach
public void clear() {
session.commit();
session.close();
}
@Param
取别名
在 MyBatis 中,@Param 是一个注解,它主要用于在 Mapper 接口的方法参数上指定参数名。
- UserMapper 接口
public interface UserMapper {
/**
* 验证登录 (@Param注解)
* @param username
* @param password
* @return
*/
User checkLoginByParam(@Param("username") String username, @Param("password") String password);
}
- UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.mapper.UserMapper">
<!--User checkLoginByParam(@Param("username") String username, @Param("password") String password);-->
<!--
这种情况会以@Param的值为 key, value 就是 后面的参数
或者可以以 param1, param2 为 key
-->
<select id="checkLoginByParam" resultType="User">
select * from t_user where username = #{param1} and password = #{param2};
</select>
</mapper>
SQL 语句返回参数
返回参数概述
数据输出总体上有两种形式:
- 增删改操作返回的受影响行数:直接使用 int 或 long 类型接收即可
- 查询操作的查询结果
.我们需要做的是,指定查询的输出数据类型即可!
并且插入场景下,实现主键数据回显示!
返回单个简单类型
retultType = 类的全限定名 | 别名
:声明返回的类型
Mapper 接口中的抽象方法
int selectEmpCount();
SQL 语句
//这里 resultType 本来返回的是 java.lang.Integer 主类可以简写为 int
<select id="selectEmpCount" resultType="int">
select count(*) from t_emp
</select>
别名问题
- 基本数据类型
int double...
->_int _double...
- 包装数据类型
Integer Double...
->int(或integer) double...
- 集合容器类型
Map LIst HashMap..
-> 小写即可map list...
- 自定义类:使用
<typeAliases>
标签
返回实体类对象
查询:返回单个实体类型,要求列名和属性名一致。才能进行实体类的属性映射:
- 解决方法1:给字段属性起别名
- 解决方法2:在配置文件配置驼峰映射 自动进行驼峰映射 emp_id -> empId
Mapper 接口的抽象方法
Employee selectEmployee(Integer empId);
SQL 语句
<!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 -->
<!-- resultType属性:指定封装查询结果的Java实体类的全类名 除非起别名 -->
<select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee">
<!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符 -->
<!-- 给每一个字段设置一个别名,让别名和Java实体类中属性名一致 -->
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{maomi}
</select>
返回 Map 类型
适用于SQL查询返回的各个字段综合起来并不和任何一个现有的实体类对应,没法封装到实体类对象中。能够封装成实体类类型的,就不使用Map类型。
列名就是 key , 查询出的列的结果就是 value
Mapper 接口的抽象方法
Map<String,Object> selectEmpNameAndMaxSalary();
SQL语句
<!-- Map<String,Object> selectEmpNameAndMaxSalary(); -->
<!-- 返回工资最高的员工的姓名和他的工资 -->
<select id="selectEmpNameAndMaxSalary" resultType="map">
SELECT
emp_name 员工姓名,
emp_salary 员工工资,
(SELECT AVG(emp_salary) FROM t_emp) 部门平均工资
FROM t_emp WHERE emp_salary=(
SELECT MAX(emp_salary) FROM t_emp
)
</select>
测试类
@Test
public void testQueryEmpNameAndSalary() {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
Map<String, Object> resultMap = employeeMapper.selectEmpNameAndMaxSalary();
Set<Map.Entry<String, Object>> entrySet = resultMap.entrySet();
for (Map.Entry<String, Object> entry : entrySet) {
//key:就是列名员工姓名, 员工工资, 部门平均工资
String key = entry.getKey();
//value: 就是查询出来的值 jerry, 999.9 659
Object value = entry.getValue();
/*
部门平均工资=659.363333333
员工姓名=jerry
员工工资=999.99
*/
log.info(key + "=" + value);
}
}
返回 List 类型
查询结果返回多个实体类对象,希望把多个实体类对象放在
List
集合中返回。此时不需要任何特殊处理,resultType 不需要指定集合类型, 只需要指定泛型即可也就是 实体类的类型。每一条记录封装成一个对象。然后全部放进 List 集合
Mapper 接口中抽象方法
List<Employee> selectAll();
SQL 语句
<!-- List<Employee> selectAll(); -->
<select id="selectAll" resultType="com.atguigu.mybatis.entity.Employee">
select emp_id empId,emp_name empName,emp_salary empSalary
from t_emp
</select>
测试
@Test
public void testSelectAll() {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
List<Employee> employeeList = employeeMapper.selectAll();
for (Employee employee : employeeList) {
System.out.println("employee = " + employee);
}
}
返回自增主键
Mybatis是将自增主键的值设置到实体类对象中,而不是以
Mapper
接口方法返回值的形式返回。
useGeneratedKeys = true
:告诉MyBatis在执行插入操作后,要尝试获取由数据库自动生成的键值(通常是主键)keyProperty = "..."
:指定主键在实体类对象中对应的属性名,Mybatis会将拿到的主键值存入这个属性
Mapper 接口中抽象方法
int insertEmployee(Employee employee);
SQL 语句
<!-- int insertEmployee(Employee employee); -->
<!-- useGeneratedKeys属性告诉MyBatis在执行插入操作后,要尝试获取由数据库自动生成的键值(通常是主键)-->
<!-- keyProperty属性可以指定主键在实体类对象中对应的属性名,Mybatis会将拿到的主键值存入这个属性 -->
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="empId">
insert into t_emp(emp_name,emp_salary)
values(#{empName},#{empSalary})
</insert>
测试类
@Test
public void testSaveEmp() {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
Employee employee = new Employee();
employee.setEmpName("john");
employee.setEmpSalary(666.66);
employeeMapper.insertEmployee(employee);
//自增主键返回给了 empId
System.out.println("employee.getEmpId() = " + employee.getEmpId());
}
插入非自增长型主键
对于不支持自增主键的数据库(比如
Oracle
),或者当你需要使用字符串类型的主键(如UUID
)时,你可以使用MyBatis
的selectKey
元素。
.
在插入记录之前,selectKey
元素会先运行。它会生成一个主键值,并将其设置到你的对象属性中。然后,MyBatis
才会执行插入操作。.
这种做法非常适合使用UUID
作为主键的情况。当然,生成主键的方法不止一种,你也可以在Java
代码中生成UUID
并手动设置到对象的属性里。不过,根据你的具体需求和场景,选择最合适的生成方式是非常重要的。
.
简单来说,selectKey
就是先帮你生成一个主键,然后MyBatis再用这个主键去插入新记录。
<selectKey>
:帮你生成一个主键,然后MyBatis再用这个主键去插入新记录。order = "before" | "after">
: sql语句是在插入语句之前还是之后执行resultType = ...
:返回值类型keyProperty = ...
:查询结果给哪个属性赋值
SQL 语句
<!--
期望, 非自增长大的主键, 交给 mybatis 帮助我们维护
-->
<insert id="insertTeacher">
<!--插入之前, 先指定一段sql语句, 生成一个主键值
order="before | after" sql语句是在插入语句之前还是之后执行
resultType = 返回值类型
keyProperty = 查询结果给哪个属性赋值
-->
<selectKey order="BEFORE" resultType="string" keyProperty="tId">
<!-- 把 UUID 中 - 换成空字符-->
select REPLACE(UUID(),'-','');
</selectKey>
INSERT INTO teacher (t_id, t_name)
VALUES(#{tId}, #{tName});
</insert>
实体类属性和数据库字段不对应解决方法总结
返回单个实体类型,要求列名和属性名一致。才能进行实体类的属性映射
- 规则要求数据库表字段命名方式:单词_单词
- 规则要求Java实体类属性名命名方式:首字母小写的驼峰式命名
实体类 和 数据库字段
tId 对应 t_id
tName 对应 t_Name
解决方法一
方案1:起别名
select t_id tId, t_name tName from teacher where t_id = #{tId}
<select id="queryById" resultType="teacher">
select t_id tId, t_name tName from teacher where t_id = #{tId}
</select>
解决方法二
在
properties
或yaml
配置文件开启驼峰式映射
mybatis:
configuration:
map-underscore-to-camel-case: true # 开启驼峰映射
~~~xml
<select id="queryById" resultType="teacher">
select * from teacher where t_id = #{tId}
</select>
解决方法三
使用自定义映射
<!--
resultMap: 设置自定义的映射关系
id: 唯一标识
type: 处理映射关系的实体类的类型
常用的标签:
id: 处理主键和实体类中实现的映射关系
result: 处理普通字段和实体类中属性的映射关系
column: 设置映射关系中的字段名, 必须是SQL查询出的某个字段
property: 设置映射关系中的属性的属性名, 必须是处理的实体类类型中的属性名
-->
<resultMap id="tMap" type="teacher">
<id column="t_id" property="tId"/>
<result column="t_name" property="tName"/>
</resultMap>
<select id="queryById" resultType="tMap">
select * from teacher where t_id = #{tId}
</select>
MyBatis 多表映射
实体类设计方案
对一
例如一个订单对应一个顾客*
.
实体类设计:对一关系下,类中只要包含单个对方对象类型属性即可!
//顾客表
public class Customer {
private Integer customerId;
private String customerName;
}
//订单表
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;// 体现的是对一的关系: 包含单个对方类型对象
}
对多
例如一个顾客对应多个订单
.
实体类设计:对多关系下,类中只要包含对方类型集合属性即可!
public class Customer {
private Integer customerId;
private String customerName;
private List<Order> orderList;// 体现的是对多的关系,包含对方集合类型
}
public class Order {
private Integer orderId;
private String orderName;
}
总结
对一
,属性中包含对方对象对多
,属性中包含对方对象集合
只有真实发生多表查询时,才需要设计和修改实体类,否则不提前设计和修改实体类!
无论多少张表联查,实体类设计都是两两考虑!【就是一次考虑两张表之间的关系就行】
- 比如 第一张表和第二张表。第二张表和第三张表这样
.在查询映射的时候,只需要关注本次查询相关的属性!例如:查询订单和客户的关系。不要关注客户对订单的关系。
自定义映射 <resultMap>
<!--
resultMap: 设置自定义的映射关系
id: 唯一标识
type: 处理映射关系的实体类的类型
常用的标签:
id: 处理主键和实体类中实现的映射关系
result: 处理普通字段和实体类中属性的映射关系
column: 设置映射关系中的字段名, 必须是SQL查询出的某个字段
property: 设置映射关系中的属性的属性名, 必须是处理的实体类类型中的属性名
-->
<resultMap id="tMap" type="teacher">
<id column="t_id" property="tId"/>
<result column="t_name" property="tName"/>
</resultMap>
<select id="queryById" resultType="tMap">
select * from teacher where t_id = #{tId}
</select>
对象属性赋值 <association>
[ 对一 ]
<!--自定义映射关系, 定义嵌套对象的映射关系-->
<resultMap id="orderMap" type="order">
<!--第一层属性 order 对象-->
<!-- order 的主键 id 标签-->
<id column="order_id" property="orderId" />
<!--普通列-->
<result column="order_name" property="orderName" />
<result column="order_Id" property="orderId" />
就是实体类里面还有个对象属性
<!--对象属性赋值
property 表里对象属性名
javaType 表里对象类型
-->
<association property="customer" javaType="Customer">
<id column="customer_id" property="customerId" />
<result column="customer_name" property="customerName" />
</association>
</resultMap>
集合属性赋值 <collection>
[ 对多 ]
<resultMap id="customerMap" type="customer">
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
<!--<association property="" 对一的对象进行赋值-->
<!--给集合属性赋值
property 集合属性名
ofType 集合的泛型类型
-->
<collection property="orderList" ofType="order">
<id column="order_id" property="orderId" />
<result column="order_name" property="orderName" />
<result column="customer_id" property="customerId" />
//注意这里不要考虑对一设置的对象属性 Customer
</collection>
</resultMap>
对一映射示例
需求
根据ID查询订单,以及订单关联的用户的信息!
实体类
//Customer
@Data
public class Customer {
private Integer customerId;
private String customerName;
}
//Order
@Data
public class Order {
private Integer orderId;
private String orderName;
private Integer customerId;
//一个订单 对应一个客户 对一
private Customer customer;
}
Mapper 接口
public interface OrderMapper {
//根据id查询订单信息和订单对应的客户
Order queryById(Integer id);
}
mapper.xml
<!-- namespace = 接口的全限定名-->
<mapper namespace="com.atguigu.mapper.OrderMapper">
<!--自定义映射关系, 定义嵌套对象的映射关系-->
<resultMap id="orderMap" type="order">
<!--第一层属性 order 对象-->
<!-- order 的主键 id 标签-->
<id column="order_id" property="orderId" />
<!--普通列-->
<result column="order_name" property="orderName" />
<result column="order_Id" property="orderId" />
<!--对象属性赋值
property 表里对象属性名
javaType 表里对象类型
-->
<association property="customer" javaType="Customer">
<id column="customer_id" property="customerId" />
<result column="customer_name" property="customerName" />
</association>
</resultMap>
<!--Order queryOrderById(Integer id);-->
<select id="queryById" resultMap="orderMap">
SELECT * FROM t_order tor JOIN t_customer tur
ON tor.customer_id = tur.customer_id
WHERE tor.order_id = #{id};
</select>
</mapper>
测试
public class MybatisTest {
private SqlSession session;
@BeforeEach
public void init() throws IOException {
session = new SqlSessionFactoryBuilder()
.build(
Resources.getResourceAsReader("mybatis-config.xml"))
.openSession();
}
@Test
public void testToOne() {
//查询订单和对应的客户
OrderMapper mapper = session.getMapper(OrderMapper.class);
Order order = mapper.queryById(1);
System.out.println(order);
System.out.println(order.getCustomer());
}
@AfterEach
public void clean() {
session.close();
}
}
对多映射示例
pojo
类
//Customer
@Data
public class Customer {
private Integer customerId;
private String customerName;
//一个客户对应多个订单
//对多: 装对方类型的集合即可
private List<Order> orderList;
}
//Order
@Data
public class Order {
private Integer orderId;
private String orderName;
private Integer customerId;
//一个订单 对应一个客户 对一
private Customer customer;
}
Mapper 接口
public interface CustomerMapper {
//查询所有客户信息以及客户对应的订单信息
List<Customer> queryList();
}
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">
<!-- namespace = 接口的全限定名-->
<mapper namespace="com.atguigu.mapper.CustomerMapper">
<resultMap id="customerMap" type="customer">
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
<!--<association property="" 对一的对象进行赋值-->
<!--给集合属性赋值
property 集合属性名
ofType 集合的泛型类型
-->
<collection property="orderList" ofType="order">
<id column="order_id" property="orderId" />
<result column="order_name" property="orderName" />
<result column="customer_id" property="customerId" />
<!-- customer 需不需要赋值, 不需要-->
</collection>
</resultMap>
<select id="queryList" resultMap="customerMap">
SELECT * FROM t_order tor
JOIN t_customer tur
ON tor.customer_id = tur.customer_id
</select>
</mapper>
多表映射优化 <autoMappingBehavior>
MyBatis 提供了三种自动映射列到字段或属性的模式:
NONE
:关闭自动映射功能。PARTIAL 【默认】
:仅自动映射未定义嵌套结果映射的字段。FULL
:可自动映射包括嵌套结果集在内的任何复杂结果。
.在
MyBatis
中,将autoMappingBehavior
设为full
后,进行多表关联查询并使用resultMap
映射时,若列名
与属性名
符合命名映射规则(二者相等
,或开启驼峰映射
规则也适用),对应的result 标签
便可省
略,系统能自动完成映射。
- 修改
yaml
或者properties
mybatis:
configuration:
auto-mapping-behavior: full # 多表映射可以自动省略符合驼峰式的 result 标签内容
修改 teacherMapper.xml
<resultMap id="teacherMap" type="teacher">
<id property="tId" column="t_id" />
<!-- 开启自动映射,并且开启驼峰式支持!可以省略 result!-->
<!-- <result property="tName" column="t_name" />-->
<collection property="students" ofType="student" >
<id property="sId" column="s_id" />
<!-- <result property="sName" column="s_name" />-->
</collection>
</resultMap>
MyBatis 动态语句
if 和 where 标签 【多条件动态判断】
<if>
:
- 通过
test 属性
对传入的参数做比较运算。- 当运算结果为
true
时,会将标签内的sql
语句进行拼接;若结果为false
,则不拼接标签内部语句。- 对于大于和小于的比较符号,不推荐直接写,建议使用实体符号(如
>
; 表示大于,<
; 表示小于)。.
<where>
:
- 自动添加 / 去除 where 关键字:只要其内部有任何一个 if 条件满足,就会自动添加
where
关键字;若所有if
条件都不满足,会自动去掉where
关键字。- 自动去除多余逻辑关键字:能够自动去掉多余的
and
和or
关键字,以此避免因条件不满足等情况出现语法错误,确保生成的sql
语句符合语法规范。
<!-- namespace = 接口的全限定名-->
<mapper namespace="com.atguigu.mapper.EmployeeMapper">
<!--List<Employee> query(@Param("name") String name,@Param("salary") Double salary);
假如两个都满足 where emp_name = #{name} and emp_salary = #{salary}
假如第一个满足 where emp_name = #{name}
假如第一个满足第二个不满足 where and emp_salary = #{salary} 错误
假如都不满足 where 错误
所以需要多弄一个 where 标签。自动处理这些情况
-->
<select id="query" resultType="employee">
select * from t_emp
<where>
<if test="name != null">
emp_name = #{name}
</if>
<if test="salary != null and salary > 100">
and emp_salary = #{salary}
</if>
</where>
</select>
</mapper>
set 标签【更新信息】
<set>
:
- 自动去掉多余的
,
- 自动添加
set
关键字
<!--根据员工 id 更新员工的数据, 我们要求 传入的 name 和 salary 不为 null 的才更新
int update(Employee employee);
全部满足: set emp_name = #{empNamee}, emp_salary = #{empSalary} 没问题
第一个满足: set emp_name = #{empNamee}, 多了个,
第二个满足: set emp_salary = #{empSalary} 没问题
都不满足: set 语法错误
所以需要 set 标签自动处理这些情况
set:
1.自动去掉多余的,
2.自动添加set关键字
-->
<update id="update">
update t_emp
<set>
<if test="empName != null">
emp_name = #{empName}
</if>
<if test="empSalary">
emp_salary = #{empSalary}
</if>
</set>
where emp_id = #{empId}
</update>
choose / when / otherwise 标签【多条件选一】
在多个分支条件中,仅执行一个。
- 从上到下依次执行条件判断
- 遇到的第一个满足条件的分支会被采纳
- 被采纳分支后面的分支都将不被考虑
- 如果所有的
when test
分支都不满足,那么就执行otherwise
分支
<!-- List<Employee> selectEmployeeByConditionByChoose(Employee employee) -->
<select id="selectEmployeeByConditionByChoose" resultType="com.atguigu.mybatis.entity.Employee">
select emp_id,emp_name,emp_salary from t_emp
where
<choose>
<when test="empName != null">emp_name=#{empName}</when>
<when test="empSalary < 3000">emp_salary < 3000</when>
<otherwise>1=1</otherwise>
</choose>
<!--
第一种情况:第一个when满足条件 where emp_name=?
第二种情况:第二个when满足条件 where emp_salary < 3000
第三种情况:两个when都不满足 where 1=1 执行了otherwise
-->
</select>
foreach 标签【批量操作】
collection
属性:要遍历的集合item
属性:遍历集合的过程中能得到每一个具体对象,在item属性中设置一个名字,将来通过这个名字引用遍历出来的对象separator
属性:指定当foreach标签的标签体重复拼接字符串时,各个标签体字符串之间的分隔符open
属性:指定整个循环把字符串拼好后,字符串整体的前面要添加的字符串close
属性:指定整个循环把字符串拼好后,字符串整体的后面要添加的字符串
foreach
标签中的 collection
属性注意事项
在接口里,若对于
List 类型
的参数没有使用@Param
注解去指定一个具体名字,此时在foreach
标签的collection
属性中,默认能够用“collection”
或者“list”
来引用这个List
集合,我们可以从异常信息中发现这一情况,比如出现如下异常信息:Parameter 'empList' not found. Available parameters are [arg0, collection, list]
不过在实际开发过程中,为防止因这种比较隐晦的表达方式而产生误会,更建议大家使用
@Param
注解清晰明确地声明变量的名称,之后在foreach
标签的collection
属性 里,按照@Param
注解所指定的名称去引用传入的参数。*
如果需要多次遍历 SQL
语句注意事项
需要设置
allowMultiQueries=true
<dataSource type="POOLED">
<!-- 建立数据库连接的具体信息 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
//这里设置 在 url 后面添加 allowMultiQueries=true
<property name="url" value="jdbc:mysql://localhost:3306/mybatis-example?allowMultiQueries=true"/>
<property name="username" value="root"/>
<property name="password" value="Ting123321"/>
</dataSource>
Mapper 接口
//根据 id 的批量查询
List<Employee> queryBatch(@Param("ids") List<Integer> id);
//根据 id 批量删除
List<Employee> deleteBatch(@Param("ids") List<Integer> ids);
//批量添加
int insertBatch(@Param("list") List<Employee> employeeList);
//批量修改
int updateBatch(@Param("list") List<Employee> employeeList);
Mapper.xml
<!--根据 id 批量查询-->
<select id="queryBatch" resultType="Employee">
select * from t_emp
where emp_id in
<!-- id 就是 从 ids 集合中拿值, ids 表示 list 集合-->
<!--(1,2,3)-->
<foreach collection="ids" open="(" separator="," close=")" item="id">
#{id}
</foreach>
</select>
<!--根据id的批量删除-->
<delete id="deleteBatch">
delete from t_emp where id in
<!--(1,2,3)-->
<foreach collection="ids" open="(" separator="," close=")" item="id">
#{id}
</foreach>
</delete>
<!--根据id的批量插入-->
<insert id="insetBatch">
insert into t_emp (emp_name, emp_salary)
values
<!--('xx', 200), ('xx', 200), ('xx', 200)-->
<foreach collection="list" separator="," item="employee">
(#{employee.empName}, #{employee.empSalary})
</foreach>
</insert>
<!--根据id的批量修改-->
<!--如果一个标签涉及多个语句! 需要设置允许指定多语句【把sql语句遍历多遍】 需要设置allowMultQueries = true -->
<update id="updateBatch">
<foreach collection="list" item="emp">
<!--
update t_emp set emp_name = #{emp.empName}, emp_salary = #{emp.empSalary}
where emp_id = #{emp.empId};
update t_emp set emp_name = #{emp.empName}, emp_salary = #{emp.empSalary}
where emp_id = #{emp.empId};
...
-->
update t_emp set emp_name = #{emp.empName}, emp_salary = #{emp.empSalary}
where emp_id = #{emp.empId};
</foreach>
</update>
SQL 片段抽取
<include reifd = "指定SQL片段">
<sql id="selectSql">
select * from t_emp
</sql>
<select id="query" resultType="employee">
//这里就是抽取的SQL 等价于 select * from t_emp
<include refid="selectSql" />
<where>
<if test="name != null">
emp_name = #{name}
</if>
<if test="salary != null and salary > 100">
and emp_salary = #{salary}
</if>
</where>
</select>
MyBatis 分页和逆向
SQL 分页回顾
查询数据
前面是索引,后面是查询几条数据
//从 0 所以开始查询 5 条数据
SELECT * FROM table LIMIT 0, 5
返回某页数据
- 公式:
limit ( (startPage-1) * pageSize, pageSize )
startPage
是 要查询的页码,pageSize
是 此页面总记录数
比如
0 4 8
1 5 9
2 6 10
3 7 11
这里返回 第二页数据 就是
SELECT * FROM table LIMIT ( (2-1) * 4, 4)
也就是
SELECT * FROM table LIMIT (4, 4)
刚好从索引 4 开始返回 4 条数据
分页插件 PageHelper
MyBatis 对插件进行了标准化的设计,并提供了一套可扩展的插件机制。插件可以在用于语句执行过程中进行拦截,并允许通过自定义处理程序来拦截和修改 SQL 语句、映射语句的结果等。
- 导入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.11</version>
</dependency>
- 在
mybatis-config.xml
配置分页插件
其中,
com.github.pagehelper.PageInterceptor
是PageHelper
插件的名称,dialect
属性用于指定数据库类型(支持多种数据库)MyBatis 其他插件也要这么安装
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
@Service
public class EmpServiceImpl implements EmpService {
@Autowird
private EmpMapper empMapper;
/**
* @param page 页码
* @param pageSize 每页记录数
*/
@Override
public PageResult<Emp> page(Integer page, Integer pageSize) {
//1. 设置分页参数 分页查询的结果封装到 Page 对象中
/**
Page 是 ArrayList 的子类
所以 Page 本质上是 List 类型
Page 封装了分页查询所有的信息包括总页数....
**/
PageHelper.startPage(page, pageSize);
//2. 执行查询
List<Emp> empList = empMapper.list();
//3. 解析查询结果, 并封装
Page<Emp> p = (Page<Emp>)empList;
//Total 是获取总记录数 Reuslt 是获取结果列表
return new PageREsult<Emp>(p.getTotal(), p.getResult);
}
}
逆向工程
ORM,即对象 - 关系映射技术,能使数据库操作更贴合面向对象思维。它分半自动、全自动两类:
半自动 ORM
:程序员要手动写 SQL 语句或配置文件,以此关联实体类与数据表,还得自行把查询结果转为实体对象。映射关系常用 XML 文件、注解来指定,像 MyBatis 就属此类,使用它需掌握扎实的 SQL 与数据库知识。全自动 ORM
:实体类与数据表自动映射,调用 API 操作数据库时,框架自动生成、执行 SQL 语句,还能把查询结果无缝转换成实体对象,且自动优化性能。像 Hibernate、Spring Data JPA、MyBatis - Plus 等都是,上手相对容易,无需过多钻研 SQL 语法。
逆向工程
MyBatis 的逆向工程堪称开发 “利器”,它能自动产出持久层代码与映射文件。依据数据库表结构及预设参数,迅速生成实体类、Mapper.xml 文件、Mapper 接口等,助力开发者快速搭建 DAO 层,无缝切入业务开发
.
实现逆向工程主要有两条路:借助 MyBatis Generator 插件,或是利用 Maven 插件。操作时,得给定数据库连接 URL、用户名、密码、目标表名、生成文件路径等配置参数。
.
注意:逆向工程只能生成单表crud的操作,多表查询依然需要我们自己编写!
使用 MyBatisX 插件
MyBatisX 是一个 MyBatis 的代码生成插件,可以通过简单的配置和操作快速生成 MyBatis Mapper、pojo 类和 Mapper.xml 文件。下面是使用 MyBatisX 插件实现逆向工程的步骤:
第一步:安装插件
在
IntelliJ IDEA
中打开插件市场,搜索MyBatisX
并安装。
第二步:IDEA 连接数据库
第三步:使用逆向插件
第四步:查看生成结果
MyBaits 配置文件解释
# 配置数据源信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
url: //127.0.0.1:3306/test
username: root
password: 123321
# 配置整合 MyBatis
mybatis:
# 指定 xml 文件
mapper-locations: mapper/*.xml
# 给包里的类起别名: 别名就是类名小写 比如 User user
type-aliases-package: com.example.pojo
configuration:
# 多表映射可以自动省略符合驼峰式的 result 标签内容
auto-mapping-behavior: full
# 开启驼峰映射
map-underscore-to-camel-case: true
设置类别名细节
namespace 用不了别名
mybatis:
# 给包里的类起别名: 别名就是类名小写 比如 User user
type-aliases-package: com.example.pojo
在批量声明的情况下,要更改单个别名用 @Alias("别名")
@Alias("author"): 。
public class Author{
...
}
驼峰映射
返回单个实体类的类型,要求列名和属性名一致。才能进行实体类映射
.
- 规则要求数据库表字段命名方式:单词_单词
- 规则要求Java实体类属性名命名方式:首字母小写的驼峰式命名
多表映射优化细节
MyBatis 提供了三种自动映射列到字段或属性的模式:
NONE
:关闭自动映射功能。PARTIAL 【默认】
:仅自动映射未定义嵌套结果映射的字段。FULL
:可自动映射包括嵌套结果集在内的任何复杂结果。
.在
MyBatis
中,将autoMappingBehavior
设为full
后,进行多表关联查询并使用resultMap
映射时,若列名
与属性名
符合命名映射规则(二者相等
,或开启驼峰映射
规则也适用),对应的result 标签
便可省
略,系统能自动完成映射。
# 配置整合 MyBatis
mybatis:
configuration:
# 多表映射可以自动省略符合驼峰式的 result 标签内容
auto-mapping-behavior: full
<resultMap id="teacherMap" type="teacher">
<id property="tId" column="t_id" />
<!-- 开启自动映射,并且开启驼峰式支持!可以省略 result!-->
<!-- <result property="tName" column="t_name" />-->
<collection property="students" ofType="student" >
<id property="sId" column="s_id" />
<!-- <result property="sName" column="s_name" />-->
</collection>
</resultMap>