MyBatis查询数据库

目录

1.MyBatis是什么?

2.配置MyBatis环境

3. MyBatis模式开发

4.单元测试

5.增删查改


1.MyBatis是什么?

        MyBatis是一款开源的基于Java的持久层框架,它可以帮助Java开发人员简化数据库操作,通过XML配置文件或注解,把Java对象映射到对应的数据库表中,实现对象关系和数据关系的互相转换,从而使得Java应用程序能够更简单的操作和读取数据库。

MyBatis相对于其他ORM框架最大的特点就是它提供了优秀的SQL编写和调试机制。通过MyBatis,我们可以直接编写出原生SQL语句,并通过MyBatis的一些工具,如参数映射、结果集映射等机制,把查询结果自动映射成Java对象或者List/Map等数据格式。同时,MyBatis还提供了懒加载、缓存和事务等高级特性,可以支持各种复杂的业务场景。

核心思想

         将JavaBean中的数据和SQL语句中的数据进行映射,因此,在使用MyBatis时,我们需要定义一个Mapper接口,然后在XML中描述这个接口中每个方法对应的SQL语句,并指定参数和返回值类型,最后,让MyBatis自动生成这个Mapper接口的实现类,这样就可以方便地对数据库进行操作。

持久层

        在 Java 程序开发中,数据通常被存储在关系型数据库或者非关系型数据库中,而持久层就是用来处理和管理这些数据存取操作的技术层次。持久层的作用是将应用程序中的对象转换成可存储的形式,然后将其存储到数据存储介质中,同时还要负责从数据存储介质中获取数据并将其转换成应用程序所需要的对象形式,这样就把数据存储和数据访问分开了,提高了程序的可维护性和扩展性。

持久层技术通常包括以下方面的内容:

  • 对象-关系映射(ORM):ORM将Java对象与关系型数据库之间进行了映射,使得Java对象可以像操作内存一样操作数据库,简化了开发。
  • 数据访问层(DAO):定义了对数据访问的接口,封装了数据存取的细节,使得上层业务逻辑不需要关注数据存取的具体实现方式。
  • 持久化框架:用于简化数据库访问、提高性能、降低开发难度、提供高级功能,如缓存、批量处理、分布式共享等等。

JDBC编程的缺点

  1. 繁琐的代码编写:JDBC需要开发人员自己手动编写大量的结果集映射、异常处理等代码,很容易出现重复代码和错误。

  2. 容易造成资源泄漏:使用JDBC需要显式地打开和关闭连接、Statement和ResultSet等对象,如果不仔细处理,可能会导致资源泄漏,从而影响应用程序的性能和稳定性。

  3. SQL语句的硬编码:JDBC需要直接在Java代码中编写SQL语句,这样就容易出现硬编码,对于SQL语句的修改和维护也不太方便。

  4. 难以应对复杂业务场景:如果某个应用有比较复杂的业务逻辑需求,例如多表关联查询、分页查询、事务控制等,使用JDBC来实现可能比较困难,需要编写大量的复杂代码。

MyBatis可以解决这些问题.ORM框架可以将Java对象和数据库表自动映射,并提供了更加优雅和高效的API,让开发人员专注于业务逻辑,而不是繁琐的数据访问代码。同时,ORM框架还可以提供缓存、懒加载和批处理等高级特性,从而进一步提高程序的性能和可维护性。

MyBatis与JDBC

MyBatis是基于JDBC实现的。MyBatis虽然与JDBC有很大的相似之处,但是它封装了JDBC的细节,使得开发人员可以更加便捷和高效地进行数据库操作。

在使用MyBatis进行数据访问时,程序仍然需要使用JDBC的API来获取连接对象、创建Statement对象、执行SQL语句以及处理查询结果等操作。不过,MyBatis对JDBC进行了简化和封装,提供了面向对象的方式访问数据库,通过 XML 或注解方式配置 SQL 语句,并且提供了一些高级特性,如缓存、动态 SQL 和对象关系映射(ORM)等。

MyBatis相对于JDBC的优势在于,MyBatis更加简单易用,提升了开发效率;同时,MyBatis还提供了更多的功能,如自动映射、动态 SQL 和二级缓存等,可以提升应用程序的性能和可维护性。

因此,MyBatis是一种基于JDBC实现的高级数据访问框架,它封装了JDBC的底层细节,提供了一种高级的、面向对象的方式来访问数据库,并且提供了多种高级特性,以方便开发人员进行数据访问和业务逻辑的实现。

ORM框架

ORM是对象关系映射(Object-Relational Mapping)的缩写,是一种将对象模型和关系数据库模型进行转换、映射的技术。通过ORM框架,可以将Java对象映射到关系数据库表中的行,从而通过面向对象的方式来操作数据库,而无需编写大量的SQL语句。

Java的类,对象,对象属性映射到ORM框架中是什么

类映射:在MyBatis中,Java的类映射到数据库中的某个表

对象映射:在MyBatis中,Java的对象映射到数据库中的数据行

属性映射:Java对象的属性和数据库表的列是一一对应的

2.配置MyBatis环境

1.添加MyBatis框架支持

运行后会报错

2.设置MyBatis配置信息

1.设置数据库连接相关信息

2.配置xml保存路径,xml命名规则 

classpath

        表示类路径,即应用程序的CLASSPATH环境变量所指的路径,用于指示 MyBatis 框架在类路径下查找配置文件。

/mybatis/

        是一个目录路径,表示 MyBatis 框架将在类路径下的 mybatis 目录中查找 *Mapper.xml 配置文件。

*Mapper.xml

        *是通配符,表示所有以 Mapper.xml 结尾的文件都应该被 MyBatis 框架解析和加载。

综合起来,classpath:/mybatis/*Mapper.xml 用于指示 MyBatis 框架在类路径下的 mybatis 目录中查找所有以 Mapper.xml 结尾的配置文件,并将其解析为 MyBatis 的 Mapper 映射文件。

举个例子,假设我们的应用程序类路径中有一个名为 mybatis 的目录,该目录下包含了两个 MyBatis 的映射文件:UserMapper.xmlOrderMapper.xml。那么,使用 classpath:/mybatis/*Mapper.xml 这个路径表达式,MyBatis 框架将会自动解析这两个映射文件,并将其配置信息绑定到对应的 Java 接口或类上,从而实现与数据库的数据访问操作。

至此,配置环境就完成了

3. MyBatis模式开发

先向数据库中插入一些数据

首先,创建一个实体类UserEntity,包含上述几个属性,必须对应。

接着,在Mapper接口中声明对UserEntity表进行操作的方法:

Mapper是MyBatis框架中的一个概念,用于描述一组将Java对象映射到SQL语句的规则。通常情况下,Mapper会包含一组接口和配置文件,用于定义Java对象和数据库表之间的映射关系。

然后,在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">
<mapper namespace="com.example.demo.mapper.UserMapper">
    <select id="getAll" resultType="com.example.demo.entity.UserEntity">
        select * from userinfo
    </select>
</mapper>

id对应接口中的方法名

namesapce:实现接口(包名加类名)

按照标准分层,然后,创建Service层访问Mapper 

Service层处理业务逻辑,并对外提供服务接口

创建Controller层

Controller层:负责接收用户请求,调用 Service 层处理请求,并通过视图呈现处理结果

启动项目,成功访问 

向表中插入一条数据

刷新后,更新数据库中插入的新数据

上述是最简单的查询操作

4.单元测试

 此处介绍一下单元测试的概念

SpringBoot已经内置了单元测试的依赖,不用添加支持

spring-boot-starter-test 是 Spring Boot 提供的一种测试 Starter,旨在提供各种常见的测试工具和框架,使开发人员能够更加轻松地编写、运行、管理 Spring Boot 应用程序的单元测试和集成测试

包含了以下几个主要组件:

  1. JUnit:一个广泛使用的 Java 单元测试框架,能够帮助开发人员快速构建单元测试用例,并自动执行这些测试用例;()我们此处使用的JUit5)
  2. Spring Test:一个专门为 Spring 应用程序设计的测试框架,能够帮助开发人员快速搭建 Spring 容器环境,在容器环境中执行测试用例,并验证应用程序的正确性;
  3. AssertJ:一个优秀的断言库,能够帮助开发人员编写简洁、易于理解、易于扩展的断言语句;
  4. Hamcrest:另一个流行的断言库,也提供了类似于 AssertJ 的各种断言方法;
  5. Mockito:一个强大的 Java 模拟框架,能够帮助开发人员快速创建模拟对象,并进行各种测试;
  6. JsonPath:一个用于处理 JSON 路径的库,能够帮助开发人员快速过滤、提取 JSON 数据。

优点主要有以下几个:

  1. 提升测试效率:借助 spring-boot-starter-test Starter 提供的各种测试工具和框架,开发人员能够更加轻松地编写、运行、管理单元测试和集成测试,从而大大提升测试效率;
  2. 方便集成 Spring Boot:spring-boot-starter-test Starter 与 Spring Boot 完美集成,可直接使用 Spring Boot 的自动配置机制和依赖注入机制,使得单元测试与实际应用程序开发更加无缝衔接;
  3. 简化代码编写:借助 spring-boot-starter-test Starter 提供的各种测试工具和框架,开发人员不需要编写大量的测试框架代码,只需要关注业务逻辑和测试用例的编写即可;
  4. 多样化的测试支持:spring-boot-starter-test Starter 支持多种类型的测试:单元测试、集成测试、端到端测试等等;
  5. 良好的社区支持与更新:spring-boot-starter-test Starter 是 Spring Boot 社区的核心组件之一,拥有庞大的用户群和活跃的维护者团队,能够及时更新和修复各种测试工具和框架的问题。

单元测试实现步骤

1.在要测试的类上右键Generate

会生成测试类

写测试信息.不要忘记添加注解!!

启动测试类

这是测试类的创建,向测试类中添加测试时,会报一个错误

 

意思是已经存在了这个测试类,是否向其中追加测试,直接点击Ok

单元测试就简单了解到这里

5.增删查改

下来看传参问题

当这两处,参数名称不一致,xml中应该写成什么

启动测试类会报错,参数id没找到

 修改xml文件

此时单元测试运行成功了 .

 所以xml文件的参数命名要和@Param("uid")一致..如果没有加这个注解,就要和原参数名相同

 加了注解,生效的就是注解中的参数命名

Mybatis获取动态参数的方式有两种:

1.${key}

${key} 是一种占位符语法,被称为字符串替换,用于将 SQL 语句中的参数占位符替换为实际的值。使用 ${} 语法会直接将参数替换为 SQL 字符串,而不是通过预编译语句来传递参数。在使用 ${key} 时,如果不正确地处理输入的参数,可能会导致 SQL 注入攻击。

可以打印出执行的生成的sql语句,就能看到效果了

需要先配置一下

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#配置mybatis xml 的文件路径,在resources/mapper 创建的所有表的 xml 文件
mybatis.mapper-locations=classpath:/mybatis/*Mapper.xml


#日志打印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#日志打印级别(默认是info,需要设置位debug,才能显示)
logging.level.com.example.demo=debug

然后执行测试

可以看到这是直接将参数替换为 SQL 字符串 

2.#{key}

在 MyBatis 中,#{key} 是一种占位符语法,被称为参数占位符,用于将 SQL 语句中的参数占位符替换为实际的值。它可以帮助我们在 SQL 语句中使用参数,使得 SQL 语句更加灵活和通用

具体来说,当应用程序调用 DAO 层的方法时,会传递一些参数给对应的 SQL 语句,这些参数可以通过 #{} 占位符来引用。MyBatis 会自动将 #{} 中的参数值进行预编译处理,保证输入数据的安全性,并将它们以安全的方式插入到 SQL 语句中,然后发送给数据库执行,从而防止了 SQL 注入攻击等安全问题

启动单元测试

这里的 #{uid} 是一个占位符,代表该位置需要动态替换为具体的参数值。在执行 SQL 时,MyBatis 会自动将 #{uid} 替换为实际传入的 uid 参数的值,并将其进行预编译处理,从而确保 SQL 查询语句的安全性。

日志打印看到的也不是sql语句,而是通过占位符模式进行内容的填充

这里数据是int类型的,我们换用String类型数据尝试

结果:

当将#{}替换为${} 再次进行执行.报错了 

原因:${}是直接替换字符串的,你输入的是什么就完全替换

'${username}',才不会报错,不加单引号sql语句就会出现问题

这样只能保障不报错,但是会存在安全性问题

例如登陆场景下:sql注入问题

 为了只返回一条数据,先将表中数据删除

mysql> select*from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  2 | lisi     | lisi     |       | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
1 row in set (0.00 sec)

正常行为: 

 

攻击者发送如下输入:

mysql> select 1 = '1';
+---------+
| 1 = '1' |
+---------+
|       1 |
+---------+
1 row in set (0.00 sec)

那么该 SQL 查询语句将会被替换为:

SELECT * FROM users WHERE username = '' or '1'='1' AND password = '' or '1'='1'

使用不存在的username,不存在的password也能查到用户信息

这就是使用${key}时的sql注入攻击.

将${key}替换为#{key}

占位符的方式,不会出现安全性问题 

${}在特定场景下,也有优点

此时使用#{key}会被错误地解析为字符串: 

SELECT * FROM userinfo ORDER BY id "desc"

即使设置了 ord 参数的值为 ASC 或 DESC,也无法正确地替换该字符串

需要将该 SQL 语句更改为以下形式进行直接替换:

SELECT * FROM userinfo ORDER BY id ${ord}

总结:

1.${key}   主要用于字符串替换,可以方便地将参数替换为 SQL 语句

        使用 ${key} 时需要注意安全性问题,应该避免潜在的 SQL 注入攻击

2.#{key}   主要用于预处理 SQL 语句,可以有效地防止 SQL 注入攻击

#{key}   底层原理:

在 MyBatis 中,#{} 在底层的实现中使用了 PreparedStatement 进行参数的预处理和类型转换。

当 MyBatis 执行 SQL 语句时,它会将 SQL 语句中的 #{} 占位符替换为 JDBC 的 Placeholder(如 ?),然后生成一个预编译的 SQL 语句。这个预编译的 SQL 语句会被传递给 DriverManager,并由 DriverManager 创建一个 PreparedStatement 对象。

然后,MyBatis 会将查询参数的值按照 JDBC 规范设置到 PreparedStatement 的参数位置上,然后执行 PreparedStatement 对象的查询操作,最后将查询结果映射成 Java 对象并返回给调用方。

使用预处理语句可以提高 SQL 的执行效率,避免 SQL 注入等安全问题,并且可以自动进行类型转换,让开发人员更加专注于业务逻辑的实现。

需要注意的是,由于预处理语句必须在编译和执行之间进行变量绑定,因此不能在运行时动态地构建 SQL 语句。如果需要在运行时动态地构建 SQL 语句,需要使用 ${} 实现字符串替换,并且需要注意防止 SQL 注入攻击的问题。

PreparedStatement 是 JDBC 提供的一种接口,用于提高执行 SQL 的性能和安全性 PreparedStatement 的特点:

  1. 预编译:PreparedStatement 对象在创建时会将 SQL 语句编译成一个可执行的语句,这样可以提高 SQL 的执行效率。

  2. 参数绑定:PreparedStatement 可以在执行前设置参数,可以使用占位符 (?) 或命名参数(:param)来代替实际的参数值,从而避免了 SQL 注入攻击。

  3. 批处理:PreparedStatement 支持批量处理,可以将多个 SQL 语句一次性提交到数据库执行,从而提高数据库处理的效率。

  4. 预处理语句缓存:预编译语句可以缓存,再次执行同样的 SQL 语句时,可以直接从缓存中获取已编译的语句,避免了每次都重新编译 SQL,从而提高 SQL 的执行效率。

修改密码 

运行单元测试

成功修改 

mysql> select*from userinfo ;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  2 | lisi     | 123456   |       | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
1 row in set (0.00 sec)

 在单元测试情况下,我们是不愿意改动数据库的,也就是不污染数据库

默认情况下,是会持久化改动数据库的

这里就用到@Transactional注解了

正常执行,但不污染数据库,执行时开启事务,执行结束后进行回滚操作,就不会污染数据库

看看数据库中,没有改变,说明进行了回滚...并且成功验证了程序,返回修改:1

mysql> select*from userinfo ;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  2 | lisi     | 123456   |       | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
1 row in set (0.00 sec)

 下来学习删除操作

//删除用户,返回受影响行数
    int deleteById(@Param("id")Integer id);


<delete id="deleteById">
<!--        默认返回受影响的行数-->
        delete from userinfo where id = #{id}
    </delete>

@Transactional
    @Test
    void deleteById() {
        int result = userMapper.deleteById(2);
        System.out.println("删除: "+result);
    }

执行结果

因为设置事务了,所以测试完成后进行了回滚,不会污染数据库

普通得增加模块,这里我们不进行回滚了,让数据插入到数据库中

//添加用户
    int addUser(UserEntity user);


<insert id="addUser">
        <!--        默认返回受影响的行数-->
        insert into userinfo(username,password) values(#{username},#{password})
    </insert>


@Test
    void addUser() {
        UserEntity user = new UserEntity();
        user.setUsername("zhangsan");
        user.setPassword("123456");
        int result = userMapper.addUser(user);
        System.out.println(result);
    }

执行结果,返回影响行数:1.数据库中新增了一条数据

==>  Preparing: insert into userinfo(username,password) values(?,?)
==> Parameters: zhangsan(String), 123456(String)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3e4e4c1]
1
mysql> select*from userinfo ;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  2 | lisi     | 123456   |       | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 |     1 |
|  3 | zhangsan | 123456   |       | 2023-05-18 17:21:49 | 2023-05-18 17:21:49 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
2 rows in set (0.00 sec)

添加用户得到id

//添加用户得到ID
    int addUserGetId(UserEntity user);


<insert id="addUserGetId" useGeneratedKeys="true" keyProperty="id">
        <!--返回受影响的行数  和  id-->
        <!--useGeneratedKeys="true"keyProperty="id"是否自动生成id,拿到id放到id字段中-->

        insert into userinfo(username,password) values(#{username},#{password})
    </insert>


@Test
    void addUserGetId() {
        UserEntity user = new UserEntity();
        user.setUsername("wangwu");
        user.setPassword("123456");
        int result = userMapper.addUserGetId(user);
        System.out.println("ID: "+user.getId());
    }

执行结果

JDBC Connection [HikariProxyConnection@1775046789 wrapping com.mysql.cj.jdbc.ConnectionImpl@60bb7995] will not be managed by Spring
==>  Preparing: insert into userinfo(username,password) values(?,?)
==> Parameters: wangwu(String), 123456(String)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@ef60710]
ID: 4
mysql> select*from userinfo ;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  2 | lisi     | 123456   |       | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 |     1 |
|  3 | zhangsan | 123456   |       | 2023-05-18 17:21:49 | 2023-05-18 17:21:49 |     1 |
|  4 | wangwu   | 123456   |       | 2023-05-18 17:36:28 | 2023-05-18 17:36:28 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
3 rows in set (0.00 sec)

模糊查询-concat

 使用#{key}方式的查询结果会报错

因为传递参数为string类型,会被#{}解析为字符串,因此会自动加上'',那么占位符替换后变成

'%'li'%'

mysql> select*from userinfo where username like '%'li'%';
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'li'%'' at line 1

需要借助concat来进行拼接

mysql>  select concat ('%','li','%');
+-----------------------+
| concat ('%','li','%') |
+-----------------------+
| %li%                  |
+-----------------------+
1 row in set (0.00 sec)

执行结果 

JDBC Connection [HikariProxyConnection@1622899093 wrapping com.mysql.cj.jdbc.ConnectionImpl@40fa8766] will not be managed by Spring
==>  Preparing: select * from userinfo where username like concat('%',?,'%')
==> Parameters: li(String)
<==    Columns: id, username, password, photo, createtime, updatetime, state
<==        Row: 2, lisi, 123456, , 2022-12-06 17:10:48, 2022-12-06 18:10:48, 1
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@56399b9e]
UserEntity(id=2, username=lisi, password=123456, photo=, createtime=2022-12-06T17:10:48, updatetime=2022-12-06T18:10:48, state=1)

 错误的模糊匹配查询会导致索引失效

1.like 'zhang%'

2.like '%zhang'

3.like '%zhang%'

只有第一种查询会走索引,后面两个都不会走索引,索引失效了

由于DBA和后端开发人员代码风格不同,可能会出现不兼容的问题,比如

然后我们执行该单元测试

我们发现JDBC成功执行,但是由于password映射不上pwd,pwd的值为空

此时resultType="com.example.demo.entity.UserEntity"就失效了,因为表中字段和类的属性不一样了,无法一对一直接映射了

解决方法--<resultMap>

<resultMap> 是 MyBatis 中用于映射查询结果集的标签。它能够解决以下两个问题:

  1. 解决列名与属性名不一致的问题:在数据库中,有些列名可能并不符合 Java 对象中属性的命名规范,这时可以通过 <resultMap> 映射来将查询结果集中的列名与 Java 对象中属性名进行映射,使得结果集与对象之间能够正确匹配。

  2. 解决多表关联查询的问题:在进行多表关联查询时,查询结果集通常会包含多个表中的列,这时可以使用 <resultMap> 将不同表中的列映射到不同的 Java 对象属性中,从而完成对结果集的映射转换。

第一步,设置映射关系

    <resultMap id="BaseMap" type="com.example.demo.entity.UserEntity">
    <!--设置主键   property:属性,类中的  column:字段,数据库表中的     -->
        <id property="id" column="id"></id>
    <!--设置普通参数-->
        <result property="pwd" column="password"></result>
        <result property="username" column="username"></result>
        <result property="createtime" column="createtime"></result>
        <result property="photo" column="photo"></result>
        <result property="updatetime" column="updatetime"></result>
        <result property="state" column="state"></result>
    </resultMap>

第二步,修改返回类型

    <select id="getUserById" resultMap="BaseMap">
        select * from userinfo where id = #{uid}
    </select>

执行结果

还有比较简单的方式,在sql语句中设置重命名也能成功映射

<select id="getUserByName" resultType="com.example.demo.entity.UserEntity">
        select id,username,password as pwd from userinfo where username = '${username}'
    </select>

多表联查

mysql> update userinfo set id = 1 where username = 'lisi';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
文章信息表
mysql> select*from articleinfo;
+----+-------+----------+---------------------+---------------------+-----+--------+-------+
| id | title | content  | createtime          | updatetime          | uid | rcount | state |
+----+-------+----------+---------------------+---------------------+-----+--------+-------+
|  1 | Java  | Java正文 | 2023-05-15 09:12:59 | 2023-05-15 09:12:59 |   1 |      1 |     1 |
+----+-------+----------+---------------------+---------------------+-----+--------+-------+
1 row in set (0.01 sec)

为了对应,将lisi 的id修改为1

mysql> update userinfo set id = 1 where username = 'lisi';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select*from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | lisi     | 123456   |       | 2022-12-06 17:10:48 | 2022-12-06 18:10:48 |     1 |
|  3 | zhangsan | 123456   |       | 2023-05-18 17:21:49 | 2023-05-18 17:21:49 |     1 |
|  4 | wangwu   | 123456   |       | 2023-05-18 17:36:28 | 2023-05-18 17:36:28 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
3 rows in set (0.00 sec)

创建文章表实体类

@Data
public class ArticleInfo {
    private int id;
    private String title;
    private String content;
    private LocalDateTime createtime;
    private LocalDateTime updatrtime;
    private int uid;
    private int rcount;
    private int state;
}

扩展类

package com.example.demo.entity.vo;
import com.example.demo.entity.ArticleInfo;
import lombok.Data;
@Data
public class ArticleInfoVO extends ArticleInfo {
    private String username;
}

在Mapper中创建接口

package com.example.demo.mapper;

import com.example.demo.entity.vo.ArticleInfoVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface ArticleMapper {
    //查询文章详情
    ArticleInfoVO getDetail(@Param("id")Integer id);
}

在mybatis包下创建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.example.demo.mapper.ArticleMapper">
    <select id="getDetail" resultType="com.example.demo.entity.vo.ArticleInfoVO">
        select a.*,u.username from articleinfo a
        left join userinfo u on u.id = a.uid
        where a.id = #{id}
    </select>
</mapper>

创建测试类

@SpringBootTest
class ArticleMapperTest {

    @Autowired
    private ArticleMapper articleMapper;
    @Test
    void getDetail() {
        ArticleInfoVO articleInfoVO = articleMapper.getDetail(1);
        System.out.println(articleInfoVO);
    }
}

执行单元测试

JDBC执行是正确的,但是返回只有username,并没有查询到父类的属性

其实是lombok出现了问题,打印时没包含父类的属性

解决办法:手动设置toString

public class ArticleInfoVO extends ArticleInfo {
    private String username;

    @Override
    public String toString() {
        return "ArticleInfoVO{" +
                "username='" + username + '\'' +
                "} " + super.toString();
    }
}

查看字节码

 再次执行 

JDBC Connection [HikariProxyConnection@166022233 wrapping com.mysql.cj.jdbc.ConnectionImpl@5dbb50f3] will not be managed by Spring
==>  Preparing: select a.*,u.username from articleinfo a left join userinfo u on u.id = a.uid where a.id = ?
==> Parameters: 1(Integer)
<==    Columns: id, title, content, createtime, updatetime, uid, rcount, state, username
<==        Row: 1, Java, <<BLOB>>, 2023-05-15 09:12:59, 2023-05-15 09:12:59, 1, 1, 1, lisi
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3662bdff]
ArticleInfoVO{username='lisi'} ArticleInfo(id=1, title=Java, content=Java正文, createtime=2023-05-15T09:12:59, updatrtime=null, uid=1, rcount=1, state=1)

查询一个用户的所有文章

先将插入一条文章记录

mysql> select*from articleinfo;
+----+-------+-----------+---------------------+---------------------+-----+--------+-------+
| id | title | content   | createtime          | updatetime          | uid | rcount | state |
+----+-------+-----------+---------------------+---------------------+-----+--------+-------+
|  1 | Java  | Java正文  | 2023-05-15 09:12:59 | 2023-05-15 09:12:59 |   1 |      1 |     1 |
|  2 | mysql | mysql正文 | 2023-05-19 11:14:49 | 2023-05-19 11:14:49 |   1 |      1 |     1 |
+----+-------+-----------+---------------------+---------------------+-----+--------+-------+
2 rows in set (0.00 sec)

其他代码

//查询一个用户所有文章
    List<ArticleInfoVO> getListByUid(@Param("uid")Integer uid);

<select id="getListByUid" resultType="com.example.demo.entity.vo.ArticleInfoVO">
        select a.*,u.username from articleinfo a
        left join userinfo u on u.id = a.uid
        where a.uid = #{uid}
    </select>

@Test
    void getListByUid() {
        Integer uid =1;
        List<ArticleInfoVO> list = articleMapper.getListByUid(uid);
        //使用多线程的方式,顺序可能会不同
        list.stream().parallel().forEach(System.out::println);
    }

执行

猜你喜欢

转载自blog.csdn.net/chenchenchencl/article/details/130676703