SQL
语句传参Mybatis
总体机制概括Map
类型参数List
类型CRUD
强化练习mapperXML
标签总结
mybatis
配置文件设计标签和顶层结构如下:
可以在
mybatis
的配置文件使用settings
标签设置,输出运过程SQL
日志!通过查看日志,可以判定 #{} 和 ${} 的输出效果!
settings
设置项:
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J(3.5.9 起废弃) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
---|
日志配置:
<settings>
<setting name="logImpl" value="SLF4J"/>
settings>
Mybatis 会将 SQL 语句中的 #{} 转换为问号占位符。
${} 形式传参,底层 Mybatis 做的是字符串拼接操作。
通常不会采用 ${} 的方式传值。一个特定的适用场景是:通过 Java 程序动态生成数据库表,表名部分需要 Java 程序通过参数传入;而JDBC
对于表名部分是不能使用问号占位符的,此时只能使用
结论:实际开发中,能用 #{} 实现的,肯定不用 ${}。
特殊情况: 动态的不是值,是列名或者关键字,需要使用 ${} 拼接
//注解方式传入参数!!
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column,
@Param("value") String value);
这里数据输入具体是指上层方法(例如
Service
方法)调用Mapper
接口时,数据传入的形式。
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 接口中抽象方法的声明
int insertEmployee(Employee employee);
SQL 语句
<insert id="insertEmployee">
insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary})
insert>
对应关系
结论:
Mybatis
会根据#{}
中传入的数据,加工成getXxx()
方法,通过反射在实体类对象中调用这个方法,从而获取到对应的数据。填充到#{}
解析后的问号占位符这个位置。
零散的多个简单类型参数,如果没有特殊处理,那么
Mybatis
无法识别自定义名称:
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
类型参数
Mapper
接口中抽象方法的声明
int updateEmployeeByMap(Map<String, Object> paramMap);
SQL
语句
<update id="updateEmployeeByMap">
update t_emp set emp_salary=#{empSalaryKey} where emp_id=#{empIdKey}
update>
junit
测试
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<>();
paramMap.put("empSalaryKey", 999.99);
paramMap.put("empIdKey", 5);
int result = mapper.updateEmployeeByMap(paramMap);
log.info("result = " + result);
}
//junit5会在每一个@Test方法后执行@@AfterEach方法
@AfterEach
public void clear() {
session.commit();
session.close();
}
对应关系
#{}
中写Map
中的key
使用场景
@Param
注解一个一个传入又太麻烦了。所以都封装到Map
中。数据输出总体上有两种形式:
int
或 long
类型接收即可我们需要做的是,指定查询的输出数据类型即可!
并且插入场景下,实现主键数据回显示!
Mapper
接口中的抽象方法
int selectEmpCount();
SQL
语句
<select id="selectEmpCount" resultType="int">
select count(*) from t_emp
</select>
Mybatis 内部给常用的数据类型设定了很多别名。 以 int 类型为例,可以写的名称有:int、integer、Integer、java.lang.Integer、Int、INT、INTEGER 等等。
junit
测试
@Test
public void testEmpCount() {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
int count = employeeMapper.selectEmpCount();
log.info("count = " + count);
}
细节解释:
select
标签,通过resultType
指定查询返回值类型!别名问题:
https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases
类型别名可为
Java
类型设置一个缩写名字。 它仅用于XML
配置,意在降低冗余的全限定类名书写。例如:
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
typeAliases>
当这样配置时,Blog
可以用在任何使用 domain.blog.Blog
的地方。
也可以指定一个包名,
MyBatis
会在包名下面搜索需要的Java Bean
,比如:
<typeAliases> <package name="domain.blog"/> typeAliases>
每一个在包
domain.blog
中的Java Bean
,在没有注解的情况下,会使用Bean
的首字母小写的非限定类名来作为它的别名。 比如domain.blog.Author
的别名为author
;若有注解,则别名为其注解值。见下面的例子:
@Alias("author")
public class Author {
...
}
下面是
Mybatis
为常见的Java
类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
别名 | 映射的类型 |
---|---|
_byte | byte |
_char (since 3.5.10) | char |
_character (since 3.5.10) | char |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
char (since 3.5.10) | Character |
character (since 3.5.10) | Character |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
biginteger | BigInteger |
object | Object |
object[] | Object[] |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
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=#{maomi}
select>
通过给数据库表字段加别名,让查询结果的每一列都和Java实体类中属性对应起来。
增加全局配置自动识别对应关系
在
Mybatis
全局配置文件中,做了下面的配置,select
语句中可以不给字段设置别名
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
适用于
SQL
查询返回的各个字段综合起来并不和任何一个现有的实体类对应,没法封装到实体类对象中。能够封装成实体类类型的,就不使用Map
类型。
Mapper
接口的抽象方法
Map<String,Object> selectEmpNameAndMaxSalary();
SQL
语句
<!-- Map 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>
junit
测试
@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) {
String key = entry.getKey();
Object value = entry.getValue();
log.info(key + "=" + value);
}
}
List
类型查询结果返回多个实体类对象,希望把多个实体类对象放在List集合中返回。此时不需要任何特殊处理,在
resultType
属性中还是设置实体类类型即可。
Mapper
接口中抽象方法
List<Employee> selectAll();
SQL
语句
<select id="selectAll" resultType="com.atguigu.mybatis.entity.Employee">
select emp_id empId,emp_name empName,emp_salary empSalary
from t_emp
select>
junit
测试
@Test
public void testSelectAll() {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
List<Employee> employeeList = employeeMapper.selectAll();
for (Employee employee : employeeList) {
log.info("employee = " + employee);
}
}
Mapper
接口中的抽象方法
int insertEmployee(Employee employee);
SQL
语句
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="empId">
insert into t_emp(emp_name,emp_salary)
values(#{empName},#{empSalary})
insert>
junit
测试
@Test
public void testSaveEmp() {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
Employee employee = new Employee();
employee.setEmpName("john");
employee.setEmpSalary(666.66);
employeeMapper.insertEmployee(employee);
log.info("employee.getEmpId() = " + employee.getEmpId());
}
注意:
Mybatis
是将自增主键的值设置到实体类对象中,而不是以Mapper
接口方法返回值的形式返回。
而对于不支持自增型主键的数据库(例如
Oracle
)或者字符串类型主键,则可以使用selectKey
子元素:selectKey
元素将会首先运行,id
会被设置,然后插入语句会被调用!
使用
selectKey
帮助插入UUID
作为字符串类型主键示例:
<insert id="insertUser" parameterType="User">
<selectKey keyProperty="id" resultType="java.lang.String"
order="BEFORE">
SELECT UUID() as id
selectKey>
INSERT INTO user (id, username, password)
VALUES (
#{id},
#{username},
#{password}
)
insert>
在上例中,我们定义了一个 insertUser
的插入语句来将 User
对象插入到 user
表中。我们使用 selectKey
来查询 UUID
并设置到 id
字段中。通过 keyProperty
属性来指定查询到的 UUID
赋值给对象中的 id 属性,而 resultType
属性指定了 UUID
的类型为 java.lang.String
。
需要注意的是,我们将 selectKey
放在了插入语句的前面,这是因为 MySQL
在 insert
语句中只支持一个 select
子句,而 selectKey
中查询 UUID
的语句就是一个 select
子句,因此我们需要将其放在前面。
最后,在将 User
对象插入到 user
表中时,我们直接使用对象中的 id 属性来插入主键值。使用这种方式,我们可以方便地插入 UUID
作为字符串类型主键。当然,还有其他插入方式可以使用,如使用Java
代码生成UUID
并在类中显式设置值等。需要根据具体应用场景和需求选择合适的插入方式。
将字段的别名设置成和实体类属性一致。
<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=#{maomi}
select>
关于实体类属性的约定:getXxx()方法、setXxx()方法把方法名中的
get
或set
去掉,首字母小写。
在Mybatis全局配置文件加入如下配置:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
SQL
语句中可以不使用别名
<select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee">
select emp_id,emp_name,emp_salary from t_emp where emp_id=#{empId}
select>
resultMap
使用
resultMap
标签定义对应关系,再在后面的SQL
语句中引用这个对应关系
<resultMap id="selectEmployeeByRMResultMap" type="com.atguigu.mybatis.entity.Employee">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="emp_salary" property="empSalary"/>
resultMap>
<select id="selectEmployeeByRM" resultMap="selectEmployeeByRMResultMap">
select emp_id,emp_name,emp_salary from t_emp where emp_id=#{empId}
select>
首先,需要准备一张名为
user
的表。该表包含字段id
(主键)、username
、password
。创建SQL如下:
CREATE TABLE `user` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL,
`password` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
接下来,需要定义一个实体类
User
,来对应user
表的一行数据。
@Data //lombok
public class User {
private Integer id;
private String username;
private String password;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
lombok pom 依赖
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.28version>
<scope>compilescope>
dependency>
定义一个 Mapper 接口 UserMapper,并在其中添加 user 表的增、删、改、查方法。
public interface UserMapper {
int insert(User user);
int update(User user);
int delete(Integer id);
User selectById(Integer id);
List<User> selectAll();
}
在
resources/mappers
目录下创建一个名为UserMapper.xml
的XML
文件,包含与Mapper
接口中相同的五个SQL
语句,并在其中,将查询结果映射到User
实体中。
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mapper.UserMapper">
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user(username, password)
VALUES(#{username}, #{password})
insert>
<update id="update">
UPDATE user SET username=#{username}, password=#{password}
WHERE id=#{id}
update>
<delete id="delete">
DELETE FROM user WHERE id=#{id}
delete>
<select id="selectById" resultType="user">
SELECT id, username, password FROM user WHERE id=#{id}
select>
<select id="selectAll" resultType="user">
SELECT id, username, password FROM user
select>
mapper>
位置:resources: mybatis-config.xml
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="logImpl" value="SLF4J"/>
settings>
<typeAliases>
<package name="com.atguigu.entity"/>
typeAliases>
<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://localhost:3306/mybatis-example"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
mappers>
configuration>
package com.atguigu.test;
import com.atguigu.entity.User;
import com.atguigu.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.List;
/**
* projectName: com.atguigu.test
*/
public class MyBatisTest {
private SqlSession session;
// junit会在每一个@Test方法前执行@BeforeEach方法
@BeforeEach
public void init() throws IOException {
session = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream("mybatis-config.xml"))
.openSession();
}
@Test
public void testAll(){
createTest();
updateTest();
selectByIdTest();
selectAllTest();
deleteTest();
}
@Test
public void createTest() {
User user = new User();
user.setUsername("admin");
user.setPassword("123456");
UserMapper userMapper = session.getMapper(UserMapper.class);
userMapper.insert(user);
System.out.println(user);
}
@Test
public void updateTest() {
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.selectById(1);
user.setUsername("root");
user.setPassword("111111");
userMapper.update(user);
user = userMapper.selectById(1);
System.out.println(user);
}
@Test
public void deleteTest() {
UserMapper userMapper = session.getMapper(UserMapper.class);
userMapper.delete(1);
User user = userMapper.selectById(1);
System.out.println("user = " + user);
}
@Test
public void selectByIdTest() {
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.selectById(1);
System.out.println("user = " + user);
}
@Test
public void selectAllTest() {
UserMapper userMapper = session.getMapper(UserMapper.class);
List<User> userList = userMapper.selectAll();
System.out.println("userList = " + userList);
}
// junit会在每一个@Test方法后执行@@AfterEach方法
@AfterEach
public void clear() {
session.commit();
session.close();
}
}
MyBatis
的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的XML
文件就显得相对简单。如果拿它跟具有相同功能的JDBC
代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis
致力于减少使用成本,让用户能更专注于SQL
代码。
SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
**select**
标签:
MyBatis
在查询和结果映射做了相当多的改进。一个简单查询的 select
元素是非常简单:
<select id="selectPerson"
resultType="hashmap" resultMap="自定义结构"> SELECT * FROM PERSON WHERE ID = #{id}
select>
这个语句名为 selectPerson
,接受一个 int
(或 Integer
)类型的参数,并返回一个 HashMap
类型的对象,其中的键是列名,值便是结果行中的对应值。
注意参数符号:#{id}
${key}
MyBatis
创建一个预处理语句(PreparedStatement
)参数,在 JDBC
中,这样的一个参数在 SQL
中会由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样:
// 近似的 JDBC 代码,非 MyBatis 代码...
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);
select
元素允许你配置很多属性来配置每条语句的行为细节:
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
resultType | 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。 |
resultMap | 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset )(依赖数据库驱动)。 |
statementType | 可选 STATEMENT ,PREPARED 或 CALLABLE 。这会让 MyBatis 分别使用 Statement ,PreparedStatement 或 CallableStatement ,默认值:PREPARED 。 |
**insert**
**,**update**
和 ****delete**
标签
数据变更语句 insert
,update
和 delete
的实现非常接近:
<insert
id="insertAuthor"
statementType="PREPARED"
keyProperty=""
keyColumn=""
useGeneratedKeys=""
timeout="20">
...
insert>
<update
id="updateAuthor"
statementType="PREPARED"
timeout="20">
...
update>
<delete
id="deleteAuthor"
statementType="PREPARED"
timeout="20">
...
delete>
属性 | 描述 |
---|---|
id | 在命名空间中唯一的标识符,可以被用来引用这条语句。 |
timeout | 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset )(依赖数据库驱动)。 |
statementType | 可选 STATEMENT ,PREPARED 或 CALLABLE 。这会让 MyBatis 分别使用 Statement ,PreparedStatement 或 CallableStatement ,默认值:PREPARED 。 |
useGeneratedKeys | (仅适用于 insert 和 update )这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false 。 |
keyProperty | (仅适用于 insert 和 update )指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset )。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
keyColumn | (仅适用于 insert 和 update )设置生成键值在表中的列名,在某些数据库(像 PostgreSQL )中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。 |