MyBatis入门
源码地址:https://gitee.com/jiayou111/study-mybatis.git
1. MyBatis框架的作用
主要的作用:简化持久层的开发
使用MyBatis框架实现数据库编程时,只需要指定各个功能对应的抽象方法及需要执行的SQL语句即可。
2.创建MyBatis项目
Mybatis项目是可以直接运行的,不一定与SpringMvc框架结合起来一起使用,所以在创建项目时,只需要直接创建maven项目即可,Eclipse中创建为jar项目即可。
在idea中我们直接创建一个maven项目即可。
如下图,其中项目名字根据个人喜好就好。
项目创建好之后我们添加pom文件中的依赖
添加mybatis的依赖
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
然后,还需要添加MyBatis整合Spring框架的mybatis-spring
依赖:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
由于需要整合Spring框架,所以,还需要添加Spring框架的spring-context
依赖:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
其底层实现是基于JDBC的,所以,还需要添加spring-jdbc
依赖:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
本次将使用MySQL数据库,所以,还需要添加mysql-connector-java
依赖:
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
在连接数据库时,应该使用数据库连接池,所以,还应该添加commons-dbcp
依赖:
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.7.0</version>
</dependency>
在开发完某个功能后,应该及时检查开发的功能是否可以正常运行,所以,还添加junit
单元测试依赖:
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
即pom文件如下:
<?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>StudeyMybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
3. 准备工作
整体结构,有不规范地方请自行更正!!!
先登录MySQL控制台,创建名为tedu_ums
的数据库:
CREATE DATABASE tedu_ums;
并使用这个数据库:
USE tedu_ums;
在这个数据库中,创建一张用户数据表t_user
,表中应该包含以下字段:id、用户名(username
)、密码(password
)、年龄(age
)、手机号码(phone
)、电子邮箱(email
):
CREATE TABLE t_user (
id int AUTO_INCREMENT,
username varchar(20) NOT NULL UNIQUE,
password varchar(20) NOT NULL,
age int,
phone varchar(20),
email varchar(50),
PRIMARY KEY (id)
) DEFAULT CHARSET=utf8;
4. 测试项目是否可以正常运行
在src/test/java下,创建cn.tedu.spring
包,并在这个包中创建ProjectTests
测试类,在测试类添加空白的测试方法,以测试JUnit环境是否正常:
package cn.tedu.spring;
import org.junit.Test;
public class ProjectTests {
@Test
public void contextLoads() {
System.out.println("ProjectTests.contextLoads()");
}
}
凡是在src/test下的文件,都不会参与项目最终打包、部署,所以,一般在编写单元测试时,对代码规范要求并不那么严格,但是,仍应该尽量遵循开发规范。
关于测试方法,必须:
- 必须添加
@Test
注解; - 必须使用
public
权限(从JUnit 5开始不严格要求测试方法的访问权限); - 必须使用
void
表示返回值类型; - 必须保持参数列表为空。
5. 连接数据库
在src/main/resources下创建jdbc.properties文件,并在其中配置连接数据库的相关信息(注意根据自己数据库信息更改):
mysql.url=jdbc:mysql://localhost:3306/tedu_ums?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
mysql.driver=com.mysql.cj.jdbc.Driver
mysql.username=root
mysql.password=123456
mysql.initialSize=2
mysql.maxTotal=10
接下来,需要在程序中读取以上配置信息,则在src/main/java下创建cn.tedu.spring
包,并在这个包中创建SpringConfig
类,在该类中读取以上配置,并基于这些配置信息创建javax.sql.DataSource
的对象,将该对象交给Spring框架进行管理:
@PropertySource("jdbc.properties")
//@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的<beans>,作用为:配置spring容器(应用上下文)
@Configuration
@MapperScan("cn.tedu.spring")
public class SpringConfig {
@Value("${mysql.url}")
private String url;
@Value("${mysql.driver}")
private String driver;
@Value("${mysql.username}")
private String username;
@Value("${mysql.password}")
private String password;
@Value("${mysql.initialSize}")
private Integer initialSize;
@Value("${mysql.maxTotal}")
private Integer maxTotal;
@Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl(url);
dataSource.setDriverClassName(driver);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setInitialSize(initialSize);
dataSource.setMaxTotal(maxTotal);
return dataSource;
}
}
完成后,在ProjectTests
测试类中添加新的测试方法,在测试方法中,读取Spring的配置文件,并从Spring容器中获取DataSource
对象,并调用该对象的getConnection()
方法以获取Connection
对象,如果能够成功获取对象,则表示配置信息无误,后续MyBatis框架也可以正常连接数据库:
@Test
public void getConnection() throws SQLException {
AnnotationConfigApplicationContext ac
= new AnnotationConfigApplicationContext(SpringConfig.class);
DataSource dataSource = ac.getBean("dataSource", DataSource.class);
Connection conn = dataSource.getConnection();
System.out.println(conn);
ac.close();
}
运行结果
6. 接口与抽象方法
实现的功能是:向t_user
表中插入用户数据。
在使用MyBatis时,各功能的抽象方法必须写在接口文件中,推荐使用Mapper
作为接口名称的后半部分,关于抽象方法的声明:
- 返回值类型:当需要执行的SQL语句是
INSERT
、DELETE
、UPDATE
类型之一时,将返回值设计为Integer
,在执行时,会返回“受影响的行数”,当然,也可以声明为void
,表示“不关心受影响的行数”,推荐使用Integer
; - 方法名称:自定义;
- 参数列表:根据需要执行的SQL语句中的问号
?
来决定,当方法参数较多时,也可以将多个参数进行封装,然后,使用封装的类型作为抽象方法的参数。
插入用户数据时,需要执行的SQL语句大致是:
insert into t_user (username, password, age, phone, email) values (?,?,?,?,?)
所以,应该在src/main/java下的cn.tedu.spring
包中创建User
类,用于封装各属性:
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
private String phone;
private String email;
// Getters & Setters
// toString()
}
然后,在cn.tedu.spring
包下创建UserMapper
接口,并在接口中添加抽象方法:
public interface UserMapper {
public abstract Integer aaa(User user); //public abstract
}
在后续执行时,还需要使得MyBatis知道接口文件在哪里,则需要在配置类(初始化Spring环境时被加载的类,有@Configuration
注解的类)之前添加@MapperScan
注解,以配置接口文件所在的包,所以,在SpringConfig
类的声明之前补充添加@MapperScan
注解并配置接口所在的包:
@PropertySource("jdbc.properties")
@Configuration
@MapperScan("cn.tedu.spring")
public class SpringConfig {
// ...
}
7. 配置SQL语句
在抽象方法的声明之前,根据要执行的SQL语句的种类,使用@Insert
、@Delete
、@Update
、@Select
注解中的某1个来配置SQL语句,由于本次需要执行的是INSERT
类型的SQL语句,则需要使用@Insert
注解,并在注解参数的字符串中编写SQL语句。
在编写SQL语句时,对于存在?
占位的位置,应该写为#{}
格式的占位符,占位符的括号中写上属性的名称!
例如:
public interface UserMapper {
@Insert("INSERT INTO t_user (username, password, age, phone, email) VALUES (#{username}, #{password}, #{age}, #{phone}, #{email})")
Integer aaa(User user);
}
最后,在执行之前,还得使得MyBatis明确执行时使用哪个数据源可以连接数据库,需要在Spring环境中配置一个SqlSessionFactoryBean
的对象,则在SpringConfig
类中添加:
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean;
}
全部完成后,在ProjectTests
中添加测试方法:
@Test
public void aaa() {
AnnotationConfigApplicationContext ac
= new AnnotationConfigApplicationContext(SpringConfig.class);
UserMapper userMapper = ac.getBean("userMapper", UserMapper.class);
User user = new User();
user.setUsername("mybatis");
user.setPassword("1234");
user.setAge(25);
user.setPhone("13800138001");
user.setEmail("root@qq.com");
Integer rows = userMapper.aaa(user);
System.out.println("rows=" + rows);
ac.close();
}
可以自行尝试编写其它的数据访问功能,但是,仅限于:
- 执行的是增、删、改类型的操作;
- 设计的抽象方法最多只能有1个参数。
对比DBUtils
8. 使用XML文件配置各抽象方法对应的SQL语句
使用@Insert
或相关注解配置SQL语句时,SQL语句与抽象方法的对应关系非常直观,但是,却不便于阅读、管理各SQL语句!因为在源代码中,SQL语句的表现就是一个字符串,在实际开发过程中,经常会使用到一些较长的SQL语句,如果使用1个字符串表示较长的SQL语句,在源代码就存在必须换行显示,又存在字符串拼接的问题!所以,非常不推荐使用@Insert
或相关注解来配置SQL语句!
在项目的src/main/resources中创建mappers文件夹,在此文件夹下创建一个UserMapper.xml文件。
将如下用于配置SQL语句的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">
这几行代码是不允许修改的,也是必须存在的!
在这个XML文件中,根节点必须是<mapper>
,在根节点中,必须配置namespace
属性,该属性值是这个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="cn.tedu.spring.UserMapper">
</mapper>
然后,在<mapper>
节点的子级,根据需要执行的SQL语句的种类,在<insert>
、<delete>
、<update>
、<select>
这4个节点类型中选取所需要使用的节点,这些节点都需要配置id
属性,取值就是对应的抽象方法的名称,然后,将SQL语句配置在节点的子级,例如:
<?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="cn.tedu.spring.UserMapper">
<insert id="aaa">
INSERT INTO t_user (
username, password, age, phone, email
) VALUES (
#{username}, #{password}, #{age}, #{phone}, #{email}
)
</insert>
</mapper>
在接口中设计抽象方法时,抽象方法的名称可以自定义,但是,不允许使用重载!
由于目前MyBatis框架还不知道这个XML文件的存在,所以,需要通过配置使框架能够使用该XML文件!应该先在jdbc.properties中添加配置:
mybatis.mapper-locations=classpath:mappers/*.xml
其实,此时,这个配置文件还叫jdbc.properties就已经有点不太合适了,因为其中还有其它的配置,但是,这个演示案例就暂时不修改文件名了。
然后,在SpringConfig中读取以上新增的配置:
@Value("${mybatis.mapper-locations}")
private Resource[] mapperLocations;
最后,在配置Spring框架获取SqlSessionFactoryBean
的对象时,为SqlSessionFactoryBean
配置以上读取到的属性:
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(mapperLocations);
return bean;
}
9. 使用MyBatis实现删除与修改
预先准备测试数据:
insert into t_user (username, password, age, phone, email) values
('root', '1234', 21, '13800138001', 'root@QQ.com'),
('spring', '4352', 28, '13800138009', 'spring@QQ.com'),
('random', '5635', 27, '13800138002', 'random@QQ.com'),
('mybatis', 'adef', 24, '13800138003', 'mybatis@QQ.com'),
('date', 'recv', 25, '13800138004', 'date@QQ.com'),
('calendar', '8454', 30, '13800138011', 'calendar@QQ.com'),
('string', 'xgvr', 23, '13800138005', 'string@QQ.com'),
('integer', 'df34', 25, '13800138006', 'integer@QQ.com'),
('servlet', 'hjfd', 26, '13800138007', 'servlet@QQ.com'),
('filter', '6xfd', 29, '13800138008', 'filter@QQ.com'),
('interceptor', '76fg', 22, '13800138010', 'interceptor@QQ.com');
只要是使用MyBatis框架开发持久层功能,需要开发的内容始终是:编写抽象方法,配置SQL语句。
假设需要实现:根据id删除用户数据。
需要执行的SQL语句大致是:
delete from t_user where id=?
则在UserMapper
接口中添加抽象方法:
Integer deleteById(Integer id);
然后,在SomeMapper的<mapper>
节点的子级补充配置:
<delete id="deleteById">
DELETE FROM t_user WHERE id=#{id}
</delete>
完成后,在src/test/java的cn.tedu.spring
包下创建新的测试类UserMapperTests
,编写并执行单元测试:
public class UserMapperTests {
@Test
public void deleteById() {
Integer id = 2;
Integer rows = userMapper.deleteById(id);
System.out.println("rows=" + rows);
}
// =========================================
public AnnotationConfigApplicationContext ac;
public UserMapper userMapper;
@Before
public void doBefore() {
ac = new AnnotationConfigApplicationContext(SpringConfig.class);
userMapper = ac.getBean("userMapper", UserMapper.class);
}
@After
public void doAfter() {
ac.close();
}
}
注意:如果单元测试环境是JUnit 5,需要将@Before
和@After
换成@BeforeEach
和@AfterEach
。
假设需要实现 :将所有用户的密码都改为某个值。
需要执行的SQL语句大致是:
update t_user set password=?
则在UserMapper
接口中添加抽象方法:
Integer updatePassword(String password);
然后,在SomeMapper.xml中添加配置:
<update id="updatePassword">
UPDATE t_user SET password=#{password}
</update>
完成后,在UserMapperTests
中添加测试:
@Test
public void updatePassword() {
String password = "8888";
Integer rows = userMapper.updatePassword(password);
System.out.println("rows=" + rows);
}
10. 使用MyBatis实现查询
在使用MyBatis实现查询时,在设计抽象方法时,应该使用期望的类型作为抽象方法的返回值类型。
假设需要实现:统计当前数据表中用户的数量。
则可以将抽象方法设计为:
Integer count();
在配置映射时,应该使用<select>
节点,该节点必须配置resultType
或resultMap
中的某1个属性(必须二选一):
<select id="count" resultType="java.lang.Integer">
SELECT COUNT(*) FROM t_user
</select>
完成后,测试:
@Test
public void count() {
Integer count = userMapper.count();
System.out.println("count=" + count);
}
假设需要实现:根据id查询某用户的详情。
则抽象方法设计为:
User findById(Integer id);
配置映射:
<select id="findById" resultType="cn.tedu.spring.User">
SELECT * FROM t_user WHERE id=#{id}
</select>
单元测试:
@Test
public void findById() {
Integer id = 8;
User user = userMapper.findById(id);
System.out.println(user);
}
假设需要实现:查询当前数据表中所有用户的详情。
由于本次查询时,可能返回多个用户的数据,必须将返回值类型声明为“可以表示若干个用户信息”的数据类型,可以使用数组,或List
集合,则抽象方法可以设计为:
List<User> findAll();
然后,配置映射,本次抽象方法的返回值类型是List<User>
类型,在配置resultType
属性时,不需要告诉框架“这次返回List
集合”,因为,框架能够根据抽象方法的返回值创建出返回值对象,只需要告诉框架“集合中的元素是什么类型的”,以使得“框架能够将查询结果封装到一个个的对象中”。
所以,配置映射的代码为:
<select id ="findAll" resultType="cn.tedu.spring.User">
SELECT * FROM t_user ORDER BY id LIMIT 0, 100
</select>
完成后,单元测试:
@Test
public void findAll() {
List<User> users = userMapper.findAll();
System.out.println("count=" + users.size());
for (User user : users) {
System.out.println(user);
}
}
11.在抽象方法中定义多个参数
假设需要实现:根据用户的id修改用户的电子邮箱。
需要执行的SQL语句大致是:
update t_user set email=? where id=?
抽象方法可以设计为:
Integer updateEmailById(Integer id, String email);
映射的SQL语句配置为:
<update id="updateEmailById">
UPDATE t_user SET email=#{email} WHERE id=#{id}
</update>
完成后,在UserMapperTests
中编写并执行单元测试:
@Test
public void updateEmailById() {
Integer id = 1;
String email = "user01@163.com";
Integer rows = userMapper.updateEmailById(id, email);
System.out.println("rows=" + rows);
}
如果直接执行以上单元测试,会出现如下错误:
Caused by:
org.apache.ibatis.binding.BindingException: Parameter 'email' not found. Available parameters are [arg1, arg0, param1, param2]
在错误的提示信息中,可以明确的看到:可用的参数是[arg1, arg0, param1, param2]
!
其实,在配置SQL映射时,可以使用arg0
表示抽象方法中的第1个参数,使用arg1
表示抽象方法中的第2个参数,例如:
<update id="updateEmailById">
UPDATE t_user SET email=#{arg1} WHERE id=#{arg0}
</update>
当然,也可以使用param1
表示抽象方法中的第1个参数,使用param2
表示抽象方法的第2个参数,例如:
<update id="updateEmailById">
UPDATE t_user SET email=#{param2} WHERE id=#{param1}
</update>
在默认情况下,如果抽象方法的参数超过1个,在配置SQL映射时,应该使用arg
系列的参数名称,第1个参数名使用arg0
,第2个使用arg1
,如果还有第3个、第4个甚至更多参数,以此类推,即使用arg2
、arg3
……当然,也可以使用param
系列的参数名称,只不过是从1开始顺序编号的,例如参数名为param1
、param2
、param3
、param4
……
虽然使用arg
或param
系列的名称可以解决多参数的问题,但是,会导致SQL映射的配置代码不直观的问题!MyBatis推荐的解决方法是在抽象方法的每一个参数之前添加@Param
注解,在注解中配置参数名,例如:
Integer updateEmailById(
@Param("id") Integer id,
@Param("email") String email
);
后续,在配置SQL映射时,在#{}
占位符中,需要使用的就是@Param
注解中配置的注解参数!
使用这种做法,既保证了方法的简单调用,又保证了XML文件中配置的SQL映射是直观的!
小结:如果抽象方法的参数列表中的参数超过了1个(达到2个或更多个),就必须为每一个参数添加@Param
注解,并且,在#{}
占位符中,需要使用的就是@Param
注解中配置的注解参数!
12. 动态SQL–foreach
动态SQL:根据执行时的参数不同,最终执行的SQL语句可能不同!
假设需要实现:一次性删除若干个用户数据。
需要执行的SQL语句大致是:
delete from t_user where id in (?,?,?,?,?);
以上SQL语句中,
IN
语法中的?
的数量是不确定的。
由于在SQL语句中参数的数量并不确定,同时,这些参数的类型、表现的意义却是相同的,则可以将抽象方法声明为:
Integer deleteByIds(List<Integer> ids);
其实,也可以使用数组来表示若干个id值,例如:
Integer deleteByIds(Integer[] ids);
甚至,还可以将参数声明为可变参数,例如:
Integer deleteByIds(Integer... ids);
可变参数在被处理时,本质上就是一个数组。
然后,配置SQL映射,需要使用<foreach>
节点对参数进行遍历:
<delete id="deleteByIds">
DELETE FROM t_user WHERE id IN (
<foreach collection="list" item="id" separator=",">
#{id}
</foreach>
)
</delete>
在配置<foreach>
节点,关于各属性的配置:
-
collection
:被遍历的对象,该对象可能是一个List
集合,也可能是一个数组。当抽象方法的参数只有1个,且没有添加@Param
注解时,该属性的值取决于参数的类型,当参数是List
集合类型时,取值为list
,当参数是数组或可变参数时,取值为array
;如果抽象方法的参数超过1个,则参数必然添加了@Param
注解,则该属性的值就是@Param
注解的参数值。 -
item
:遍历过程中,得到的集合或数组中的元素的名称,当确定该属性的名称后,在<foreach>
节点的子级,就可以通过#{}
占位符中填写这个名称来表示集合或数组中的某个值。 -
separator
:生成动态SQL中的SQL语句片段时,各值之间使用什么符号进行分隔。 -
open
与close
:遍历生成的SQL语句片段的最左侧字符串与最右侧字符串。
完成后,即可进行测试:
@Test
public void deleteByIds() {
List<Integer> ids = new ArrayList<Integer>();
ids.add(9);
ids.add(14);
ids.add(16);
Integer rows = userMapper.deleteByIds(ids);
System.out.println("rows=" + rows);
}
13. 动态SQL–判断与选择
在动态SQL中还可以实现if
判断的效果,需要使用<if>
节点来配置,其格式是:
<if test="表达式">
满足表达式的判断条件时的SQL片段
</if>
但是,并没有匹配的相当于else
作用的节点,所以,如果某次判断,无论是true
还是false
都有对应的配置时,可以使用2个<if>
分别使用完全相反的判断标准来进行配置。
当然,也可以使用<choose>
系列的节点实现if...else
的效果,其格式是:
<choose>
<when test="条件">
满足表达式的判断条件时的SQL片段
</when>
<otherwise>
不满足表达式的判断条件时的SQL片段
</otherwise>
</choose>
14. 关于#{}和${}格式的占位符
在MyBatis中,配置SQL映射时,可以使用#{}
或${}
格式的占位符表示某个变量。
当需要表示的是某个值时,应该使用#{}
格式的占位符,简单的说,在学习JDBC时,自行编写的SQL语句中可以使用问号?
的位置都应该使用#{}
格式的占位符。严格来说,当使用#{}
格式的占位符时,MyBatis会先使用问号?
对这些位置进行占位,然后,将SQL语句发送到MySQL服务器,MySQL服务器对例如delete from t_user where id=?
这类存在问号?
的SQL语句进行词法分析、语义分析、编译等过程,当编译通过后,再将各个值代进去执行,所以,整个过程是预编译处理的!由于是使用预编译处理的,所以,在使用各个值时,并不需要关心数据类型的问题,也不存在SQL注入的风险!
当需要表示的是SQL语句中的某个片段时,应该使用${}
格式的占位符,凡在SQL语句中不可以写成问号?
的部分必须使用${}
格式的占位符。当使用${}
格式的占位符时,不可能使用预编译的做法,因为例如select * from t_user where ?
这样的SQL语句是不正常的,甚至有些还是不合法的!MyBatis在处理时,必须先将${}
占位符的值与所配置的SQL语句进行拼接,然后再执行词法分析、语义分析、编译等过程,如果编译通过,则直接执行(值在这之前就已经代进去了)。由于在编译之前就把${}
占位符的值已经代入到SQL语句中了,所以,必须严格区分在完整的SQL语句中的各个值的数据类型!同时,还存在SQL注入的风险!
懒汉式小结:当需要使用占位符表示某个参数值是,全部使用#{}
的格式,如果发现该格式的无效,则改用${}
格式。
小结:使用#{}
格式的占位符只能表示SQL语句中的某个值,在处理过程中是预编译的,可以无视值的数据类型,没有SQL注入的风险!使用${}
格式的占位符可以表示SQL语句中的任何片段,是直接与SQL语句进行拼接再编译、执行的,必须严格表现值的数据类型,且存在SQL注入的风险!
15. 解决查询时名称不匹配导致无法封装数据的问题【1】
在MyBatis处理查询时,会自动将“查询结果中的列名”与“封装查询结果的属性名”进行对照,如果一致,则会将查询结果中的值封装到对应的属性中!
例如在查询结果中存在名为username
的列,值是root
,同时,该查询返回的结果是User
类型的,且User
类中存在名为username
的属性,则MyBatis会将root
封装到User
类对象的username
属性中!
在配置SQL语句时,可以自定义别名,使得查询结果中的列名与属性名完全一致,则MyBatis就可以自动完成封装:
<select id="findAll" resultType="cn.tedu.spring.User">
SELECT
id, username, password, age,
phone, email, group_id AS groupId
FROM t_user ORDER BY id LIMIT 0, 100
</select>
16. 解决查询时名称不匹配导致无法封装数据的问题【2】
当名称不匹配时,还可以在XML文件中配置<resultMap>
节点,以指导MyBatis如何完成正确的封装!例如:
<!-- id:自定义的名称 -->
<!-- type:封装查询结果的数据类型 -->
<resultMap type="cn.tedu.spring.User" id="UserMap">
<!-- column:查询结果中的列名 -->
<!-- property:封装查询结果的类中的属性名 -->
<result column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="age" property="age"/>
<result column="phone" property="phone"/>
<result column="email" property="email"/>
<result column="group_id" property="groupId"/>
</resultMap>
配置以上<resultMap>
使用时,<select>
节点就必须配置resultMap
属性,并且,不允许再配置resultType
属性:
<select id="findById" resultMap="UserMap">
SELECT * FROM t_user WHERE id=#{id}
</select>
在执行单表数据查询时,在配置<resultMap>
时,如果查询结果的列名与类中的属性名本来就是完全一致的,则可以不必配置对应的<result>
子节点!也就是以上配置可以改为:
<resultMap type="cn.tedu.spring.User" id="UserMap">
<result column="group_id" property="groupId"/>
</resultMap>
另外,关于主键的配置,推荐使用<id>
节点来配置,而不要使用<result>
节点,并且,无论主键字段的名称是否匹配,都推荐显式的配置出来,也就是说,以上配置的推荐代码应该是:
<!-- id:自定义的名称 -->
<!-- type:封装查询结果的数据类型 -->
<resultMap type="cn.tedu.spring.User" id="UserMap">
<!-- column:查询结果中的列名 -->
<!-- property:封装查询结果的类中的属性名 -->
<id column="id" property="id"/>
<result column="group_id" property="groupId"/>
</resultMap>
关于<resultMap>
的配置小结:
- 使用
<id>
节点来配置主键的对应关系,不要使用<result>
来配置,并且,即使名称完全一致,也应该配置; - 在执行单表数据查询时,如果名称本来就是完全一致的,则可以不必配置对应的
<result>
子节点!
17. 一对一关系的关联查询
假设需要实现:根据id查询某个用户详情时,显示该用户归属的组的名称!
需要执行的SQL语句大致是:
select
t_user.*, t_group.name
from t_user left join t_group on t_user.group_id=t_group.id where t_user.id=10;
项目的实体类都是不满足关联查询需求的!因为每1个实体类都是与每1张数据表相对应的,决不可能存在某1个实体类可以对应多张表的关联查询结果,为了封装关联查询的结果,需要创建对应的VO
类:
public class UserVO {
private Integer id;
private String username;
private String password;
private Integer age;
private String phone;
private String email;
private Integer groupId;
private String groupName;
// Getters & Setters
// toString()
}
实体类与VO类,从代码设计方面来看,几乎是一样的,只不过,这2种类的定位不同,实体类是需要与数据表相对应的,而VO类是需要与查询结果相对应的!
设计的抽象方法为:
UserVO findVOById(Integer id);
映射的SQL语句是:
<select id="findVOById" resultType="cn.tedu.spring.UserVO">
SELECT
t_user.id, username, password, age, phone, email, group_id AS groupId,
t_group.name AS groupName
FROM
t_user
LEFT JOIN
t_group
ON
t_user.group_id=t_group.id
WHERE
t_user.id=#{id}
</select>
注意:在关联查询时,一定不要使用星号*
表示字段列表!
【阶段小结】当查询时,如果出现名称不匹配的问题(查询结果中的列名与封装结果的数据类型中的属性名)时,可以使用自定义别名的方式,使得列名与属性名一致,也可以使用<resultMap>
指导MyBatis进行封装,暂定的规则是:当查询允许使用星号(*
)表示字段列表时,应该使用<resultMap>
进行配置,当查询不允许使用星号(*
)时,就需要自行穷举字段列表,就顺便自定义别名,以解决名称不匹配的问题。
根据id查询某个用户组的详情时,显示该组的所有用户的信息!需要执行的SQL语句大致是:
select * from t_group left join t_user on t_group.id=t_user.group_id where t_group.id=3;
18. 一对多关系的关联查询
假设需要实现:根据id查询某个用户组的详情时,显示该组的所有用户的信息!
需要执行的SQL语句大致是:
select * from t_group left join t_user on t_group.id=t_user.group_id where t_group.id=1;
首先,需要在项目中创建新的GroupVO
类,用于封装查询结果:
public class GroupVO {
private Integer id;
private String name;
private List<User> users;
}
在GroupMapper
接口中添加抽象方法:
GroupVO findVOById(Integer id);
在GroupMapper.xml文件中配置SQL映射:
<resultMap id="GroupMap" type="cn.tedu.spring.GroupVO">
<!-- column:查询结果中的列名 -->
<!-- property:封装查询结果的类中的属性名 -->
<id column="gid" property="id" />
<result column="name" property="name" />
<!-- collection节点:用于配置1对多的属性,也就是List集合类型的属性 -->
<!-- ofType属性:List集合中的元素的类型 -->
<!-- 在collection节点的子级的各id、result节点中的property指的是ofType的类中的属性名 -->
<collection property="users" ofType="cn.tedu.spring.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="age" property="age"/>
<result column="phone" property="phone"/>
<result column="email" property="email"/>
<result column="group_id" property="groupId"/>
</collection>
</resultMap>
<select id="findVOById" resultMap="GroupMap">
SELECT
t_group.id AS gid, name,
t_user.*
FROM
t_group
LEFT JOIN
t_user
ON
t_group.id=t_user.group_id
WHERE
t_group.id=#{id}
</select>
【MyBatis阶段小结】
-
【理解】MyBatis的主要作用:简化持久层开发;
-
【掌握】使用MyBatis框架时需要添加的依赖;
-
【认识】使用MyBatis框架时必要的配置;
-
【掌握】抽象方法的设计原则:
- 返回值:如果是增、删、改类型的操作,使用
Integer
作为返回值类型;如果是查询类型的操作,可以使用期望的类型作为返回值类型,只要能把查询结果封装进去就行; - 方法名称:自定义,但是不允许重载;
- 参数列表:根据需要执行的SQL语句中的参数来设计抽象方法的参数列表,简单的说,就是SQL语句中有哪些问号,在抽象方法中就设计哪些参数,当参数较多时,还可以使用封装的类型作为参数,使得抽象方法中的1个参数就可以表示SQL语句中的若干个参数,当抽象方法的参数超过1个时,必须为每个参数都配置
@Param
注解。
- 返回值:如果是增、删、改类型的操作,使用
-
【掌握】配置SQL映射:
- 通常,每个接口文件都有1个对应的XML文件,在配置SQL语句的XML中,必须在根节点
<mapper>
中配置namespace
属性指定对应的接口文件; - 需要根据所执行的SQL语句的种类来选取
<insert>
、<delete>
、<update>
、<select>
节点,每个节点都必须配置id
属性指定对应的抽象方法的名称,<select>
还必须配置resultType
或resultMap
中的某1个属性。
- 通常,每个接口文件都有1个对应的XML文件,在配置SQL语句的XML中,必须在根节点
-
【理解】
#{}
和${}
格式的占位符的区别; -
【掌握】解决查询时,查询结果中的列名与封装结果的类的属性名不一致的问题:
- 在SQL语句中指定列的别名,使得查询结果中的列名能与类的属性名匹配;
- 配置
<resultMap>
节点,以指定MyBatis框架完成封装过程。
-
【掌握】动态SQL的
<foreach>
节点的使用; -
【了解】动态SQL的
<if>
和<choose>
系列节点的使用; -
【理解】实体类与VO类的定位与区别;
-
【掌握】
<resultMap>
的配置与使用; -
【理解】在处理查询时,什么时候需要自定义别名:
- 在设计SQL语句中,不使用星号(
*
)表示字段列表,且存在名称不匹配的问题时,例如实现1对1的关联查询时; - 在关联查询时,查询结果中出现了名称完全相同的列名时,必须通过自定义别名,使得查询结果中的每个列名都不同。
- 在设计SQL语句中,不使用星号(
-
【理解】在处理查询时,什么时候需要配置
<resultMap>
:- 在SQL语句中使用了星号(
*
)表示字段列表,且存在名称不匹配的问题时,配置<resultMap>
便于应用到多个不同的查询中; - 需要实现1对多的关联查询时。
- 在SQL语句中使用了星号(