MyBatis入门

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语句是INSERTDELETEUPDATE类型之一时,将返回值设计为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/javacn.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>节点,该节点必须配置resultTyperesultMap中的某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个甚至更多参数,以此类推,即使用arg2arg3……当然,也可以使用param系列的参数名称,只不过是从1开始顺序编号的,例如参数名为param1param2param3param4……

虽然使用argparam系列的名称可以解决多参数的问题,但是,会导致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语句片段时,各值之间使用什么符号进行分隔。

  • openclose:遍历生成的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>还必须配置resultTyperesultMap中的某1个属性。
  • 【理解】#{}${}格式的占位符的区别;

  • 【掌握】解决查询时,查询结果中的列名与封装结果的类的属性名不一致的问题:

    • 在SQL语句中指定列的别名,使得查询结果中的列名能与类的属性名匹配;
    • 配置<resultMap>节点,以指定MyBatis框架完成封装过程。
  • 【掌握】动态SQL的<foreach>节点的使用;

  • 【了解】动态SQL的<if><choose>系列节点的使用;

  • 【理解】实体类与VO类的定位与区别;

  • 【掌握】<resultMap>的配置与使用;

  • 【理解】在处理查询时,什么时候需要自定义别名:

    • 在设计SQL语句中,不使用星号(*)表示字段列表,且存在名称不匹配的问题时,例如实现1对1的关联查询时;
    • 在关联查询时,查询结果中出现了名称完全相同的列名时,必须通过自定义别名,使得查询结果中的每个列名都不同。
  • 【理解】在处理查询时,什么时候需要配置<resultMap>

    • 在SQL语句中使用了星号(*)表示字段列表,且存在名称不匹配的问题时,配置<resultMap>便于应用到多个不同的查询中;
    • 需要实现1对多的关联查询时。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

boy快快长大

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值