Mybatis学习笔记

MyBatis的简介

Mybatis历史#

  • MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下,iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github
  • iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBatis提供的持久层框架包括SQL Maps和Data Access Objects(DAO)

原始jdbc操作的分析#

原始jdbc开发存在的问题如下:

  • 数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能

  • sql 语句在代码中硬编码,耦合度高,造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变java代码。

  • 查询操作时,需要手动将结果集中的数据手动封装到实体中。插入操作时,需要手动将实体的数据设置到 sql 语句的占位符位置

应对上述问题给出的解决方案:

  • 使用数据库连接池初始化连接资源
  • 将sql语句抽取到xml配置文件中
  • 使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射

什么是MyBatis?#

  • MyBatis是一个优秀的基于java的持久层框架,它内部封装了 jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
  • MyBatis通过xml或注解的方式将要执行的各种 statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句。
  • 最后MyBatis框架执行sql并将结果映射为java对象并返回。采用ORM思想解决了实体和数据库映射的问题,对jdbc 进行了 封装,屏蔽了jdbc api 底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。
  • MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架

MyBatis参考文档:https://mybatis.org/mybatis-3/zh/index.html

MyBatis的快速入门

MyBatis开发步骤:

  • 添加MyBatis的坐标
  • 创建user数据表
  • 编写User实体类
  • 编写映射文件UserMapper.xml
  • 编写核心文件SqlMapConfig.xml

环境搭建#

1、导入MyBatis的坐标和其他相关坐标

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.23</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

2、创建user数据表

3、创建User实体类

public class User {

    private int id;
    private String username;
    private String password;
    // 省略set,get方法
}

4、编写UserMapper映射文件

<?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="userMapper">
    <!--查询操作-->
    <select id="findAll" resultType="com.itheima.domain.User">
        select * from user
    </select>
</mapper>

5、编写MyBatis核心文件

习惯上命名为mybatis-config.xml,这个文件名仅仅只是建议,并非强制要求。将来整合Spring之后,这个配置文件可以省略。
核心配置文件主要用于配置连接数据库的环境以及MyBatis的全局配置信息,核心配置文件存放的位置是src/main/resources目录下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--通过properties标签加载外部properties文件-->
    <properties resource="jdbc.properties"></properties>

    <!--数据源环境-->
    <environments default="developement">
        <environment id="developement">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    
    <!--加载映射文件-->
    <mappers>
        <mapper resource="com/itheima/mapper/UserMapper.xml"></mapper>
    </mappers>

</configuration>

编写测试代码#

以测试查询list为例:

// 加载核心配置文件(路径相对于resources目录)
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 获得sqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 获得sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 执行sql语句 参数:namespace+id
List<User> userList = sqlSession.selectList("userMapper.findAll");
// 打印结果
System.out.println(userList);
// 释放资源
sqlSession.close();

加入log4j日志功能#

1.加入依赖

<!-- log4j日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

加入log4j的配置文件

  • log4j的配置文件名为log4j.xml,存放的位置是src/main/resources目录下

  • 日志的级别:FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试) 从左到右打印的内容越来越详细

  • <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
        <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
            <param name="Encoding" value="UTF-8" />
            <layout class="org.apache.log4j.PatternLayout">
    			<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
            </layout>
        </appender>
        <logger name="java.sql">
            <level value="debug" />
        </logger>
        <logger name="org.apache.ibatis">
            <level value="info" />
        </logger>
        <root>
            <level value="debug" />
            <appender-ref ref="STDOUT" />
        </root>
    </log4j:configuration>
    

MyBatis的映射文件概述

  • 相关概念:ORM(Object Relationship Mapping)对象关系映射。
  • 对象:Java的实体类对象
    • 关系:关系型数据库
    • 映射:二者之间的对应关系
Java概念 数据库概念
属性 字段/列
对象 记录/行
  • 映射文件的命名规则
  • 表所对应的实体类的类名+Mapper.xml
    • 例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml
    • 因此一个映射文件对应一个实体类,对应一张表的操作
    • MyBatis映射文件用于编写SQL,访问以及操作表中的数据
    • MyBatis映射文件存放的位置是src/main/resources/mappers目录下
  • MyBatis中可以面向接口操作数据,要保证两个一致
  • mapper接口的全类名和映射文件的命名空间(namespace)保持一致
    • mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致

MyBatis的增删改查操作

插入操作#

编写UserMapper.xml

<!--插入操作-->
<insert id="save" parameterType="com.itheima.domain.User">
	insert into user values(#{id},#{username},#{password})
</insert>

注意区分${}#{}

${} 是 Properties ⽂件中的变量占位符,它可以⽤于标签属性值和 sql 内部,属于静态⽂本替换,⽐如${driver}会被静态替换为 com.mysql.cj.jdbc.Driver

#{} 是 sql 的参数占位符,Mybatis 会将 sql 中的 #{} 替换为?号,在 sql 执⾏前会使⽤PreparedStatement 的参数设置⽅法,按序给 sql 的?号占位符设置参数值,⽐如 ps.setInt(0, parameterValue)#{item.name} 的取值⽅式为使⽤反射从参数对象中获取,item 对象的 name 属性值,相当于 param.getItem().getName()

编写插入实体User的代码

@Test
//插入操作
public void test2() throws IOException {

    //模拟user对象
    User user = new User();
    user.setUsername("xxxx");
    user.setPassword("abc");

    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    sqlSession.insert("userMapper.save", user);
    //mybatis执行更新操作  提交事务
    sqlSession.commit();
    sqlSession.close();
}

插入操作注意问题

  • 插入语句使用insert标签
  • 在映射文件中使用parameterType属性指定要插入的数据类型
  • Sql语句中使用#{实体属性名}方式引用实体中的属性值
  • 插入操作使用的API是sqlSession.insert(“命名空间.id”,实体对象);
  • 插入操作涉及数据库数据变化,所以要使用sqlSession对象显示的提交事务,即sqlSession.commit()

删除操作#

编写UserMapper.xml

<!--删除操作-->
<delete id="delete" parameterType="int">
	delete from user where id=#{id}
</delete>

编写删除数据的代码

@Test
//删除操作
public void test4() throws IOException {
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    sqlSession.delete("userMapper.delete", 8);
    sqlSession.commit();
    sqlSession.close();
}

删除操作注意问题

  • 删除语句使用delete标签
  • Sql语句中使用#{任意字符串}方式引用传递的单个参数
  • 删除操作使用的API是sqlSession.delete(“命名空间.id”,Object);

查询操作#

编写UserMapper.xml

<!--查询操作-->
<select id="findAll" resultType="user">
	select * from user
</select>

<!--根据id进行查询-->
<select id="findById" resultType="user" parameterType="int">
	select * from user where id=#{id}
</select>

单个查询和列表查询

@Test
//查询一个对象
public void test5() throws IOException {
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    User user = sqlSession.selectOne("userMapper.findById", 1);
    System.out.println(user);
    sqlSession.close();
}

@Test
//查询列表
public void test1() throws IOException {
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    List<User> userList = sqlSession.selectList("userMapper.findAll");
    System.out.println(userList);
    sqlSession.close();
}
  1. 查询的标签select必须设置属性resultType或resultMap,用于设置实体类和数据库表的映射关系
    • resultType:自动映射,用于属性名和表中字段名一致的情况
    • resultMap:自定义映射,用于一对多或多对一或字段名和属性名不一致的情况
  2. 当查询的数据为多条时,不能使用实体类作为返回值,只能使用集合,否则会抛出异常TooManyResultsException;但是若查询的数据只有一条,可以使用实体类或集合作为返回值

修改操作#

<!--修改操作-->
<update id="update" parameterType="com.itheima.domain.User">
	update user set username=#{username},password=#{password} where id=#{id}
</update>
@Test
//修改操作
public void test3() throws IOException {

    //模拟user对象
    User user = new User();
    user.setId(7);
    user.setUsername("lucy");
    user.setPassword("123");

    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //执行操作  参数:namespace+id
    sqlSession.update("userMapper.update", user);
    sqlSession.commit();
    sqlSession.close();
}

修改操作注意问题

  • 修改语句使用update标签
  • 修改操作使用的API是sqlSession.update(“命名空间.id”,实体对象);

#{}和${}区别

  • MyBatis获取参数值的两种方式:${}和#{}
  • ${}的本质就是字符串拼接,#{}的本质就是占位符赋值
  • ${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号

Mybatis获取参数的方式

单个字面量类型的参数#

若mapper接口中的方法参数为单个的字面量类型,此时可以使用${}和#{}以任意的名称(最好见名识意)获取参数的值,注意${}需要手动加单引号

<!--User getUserByUsername(String username);-->
<select id="getUserByUsername" resultType="User">
	select * from t_user where username = #{username}
</select>
<!--User getUserByUsername(String username);-->
<select id="getUserByUsername" resultType="User">  
	select * from t_user where username = '${username}'  
</select>

多个字面量类型的参数#

  • 若mapper接口中的方法参数为多个时,此时MyBatis会自动将这些参数放在一个map集合中
  1. 以arg0,arg1...为键,以参数为值;

  2. 以param1,param2...为键,以参数为值

  • 因此只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号。
  • 使用arg或者param都行,要注意的是,arg是从arg0开始的,param是从param1开始的
<!--User checkLogin(String username,String password);-->
<select id="checkLogin" resultType="User">  
	select * from t_user where username = #{arg0} and password = #{arg1}  
</select>
<!--User checkLogin(String username,String password);-->
<select id="checkLogin" resultType="User">
	select * from t_user where username = '${param1}' and password = '${param2}'
</select>

map集合类型的参数#

若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在map中只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号

<!--User checkLoginByMap(Map<String,Object> map);-->
<select id="checkLoginByMap" resultType="User">
	select * from t_user where username = #{username} and password = #{password}
</select>
@Test
public void checkLoginByMap() {
	SqlSession sqlSession = SqlSessionUtils.getSqlSession();
	ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
	Map<String,Object> map = new HashMap<>();
	map.put("usermane","admin");
	map.put("password","123456");
	User user = mapper.checkLoginByMap(map);
	System.out.println(user);
}

实体类型的参数#

若mapper接口中的方法参数为实体类对象时此时可以使用${}和#{},通过访问实体类对象中的属性名获取属性值,注意${}需要手动加单引号

<!--int insertUser(User user);-->
<insert id="insertUser">
	insert into t_user values(null,#{username},#{password},#{age},#{sex},#{email})
</insert>
@Test
public void insertUser() {
	SqlSession sqlSession = SqlSessionUtils.getSqlSession();
	ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
	User user = new User(null,"Tom","123456",12,"男","123@321.com");
	mapper.insertUser(user);
}

使用@Param标识参数#

可以通过@Param注解标识mapper接口中的方法参数,此时,会将这些参数放在map集合中

  1. 以@Param注解的value属性值为键,以参数为值;
  2. 以param1,param2...为键,以参数为值;
  • 只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号
<!--User CheckLoginByParam(@Param("username") String username, @Param("password") String password);-->
    <select id="CheckLoginByParam" resultType="User">
        select * from t_user where username = #{username} and password = #{password}
    </select>
@Test
public void checkLoginByParam() {
	SqlSession sqlSession = SqlSessionUtils.getSqlSession();
	ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
	mapper.CheckLoginByParam("admin","123456");
}

MyBatis的核心配置文件概述

MyBatis核心配置文件层级关系#

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

MyBatis常用配置解析#

1) environments标签#

?xml version="1.0" encoding='UTF-8' ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--读取属性文件(jdbc.properties)
    属性
        resource:从resources目录下找指定名称的文件加载
        url:使用绝对路径加载属性文件 (D:\...)
    -->
    <properties resource="jdbc.properties"></properties>
    <!-- 配置环境变量
    只需要更改default的名字 去使用不同的id即可使用不同的数据库配置-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <typeAliases>
       <package name="com.entity"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <!-- 配置事务管理器
            type:指定事务管理的方式 JDBC/MANAGED(Spring)-->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置数据源
            type:不同的配置方式
            JNDI :java命令目录接口 在服务器端进行数据库连接池管理
            POOLED:使用数据库连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driverClassName}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>

        <environment id="online">
            <transactionManager type=""></transactionManager>
            <dataSource type=""></dataSource>
        </environment>

    </environments>
    <mappers>
        <mapper resource="StudentMapper.xml"></mapper>
        <!-- 优先级packing>resource>class -->
    </mappers>
</configuration>

事务管理器(transactionManager)

在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):

  • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。

  • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:

    <transactionManager type="MANAGED">
      <property name="closeConnection" value="false"/>
    </transactionManager>
    

数据源(dataSource)类型有三种:

  • UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
  • POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。
  • JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。

2)mapper标签#

该标签的作用是加载映射的,需要告诉 MyBatis 到哪里去找到这些sql语句。

加载方式有如下几种:

  • 使用相对于类路径的资源引用,例如:<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  • 使用完全限定资源定位符(URL),例如:<mapper url="file:///var/mappers/AuthorMapper.xml"/>
  • 使用映射器接口实现类的完全限定类名,例如:<mapper class="org.mybatis.builder.AuthorMapper"/>
  • 将包内的映射器接口实现全部注册为映射器,例如:<package name="org.mybatis.builder"/>

3)properties标签#

snipaste20220402_134423.jpg

4)typeAliases标签#

snipaste20220402_134441.jpg

下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

MyBatis的相应API

SqlSession工厂构建器SqlSessionFactoryBuilder#

常用API:SqlSessionFactory build(InputStream inputStream)

通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

其中, Resources 工具类,这个类在 org.apache.ibatis.io 包中。Resources 类帮助你从类路径下、文件系统或 一个 web URL 中加载资源文件。

SqlSession工厂对象SqlSessionFactory#

SqlSession openSession()
SqlSession openSession(boolean autoCommit) // 参数为是否自动提交,如果设置为true,那么不需要手动提交事务

默认的 openSession() 方法没有参数,它会创建具备如下特性的 SqlSession:

  • 事务作用域将会开启(也就是不自动提交)。
  • 将由当前环境配置的 DataSource 实例中获取 Connection 对象。
  • 事务隔离级别将会使用驱动或数据源的默认设置。
  • 预处理语句不会被复用,也不会批量处理更新。

SqlSession会话对象#

SqlSession 在 MyBatis 中是非常强大的一个类。它包含了所有执行语句、提交或回滚事务以及获取映射器实例的方法。

语句执行方法#

这些方法被用来执行定义在 SQL 映射 XML 文件中的 SELECT、INSERT、UPDATE 和 DELETE 语句。你可以通过名字快速了解它们的作用,每一方法都接受语句的 ID 以及参数对象,参数可以是原始类型(支持自动装箱或包装类)、JavaBean、POJO 或 Map。

<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<T> Cursor<T> selectCursor(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)

selectOne 和 selectList 的不同仅仅是 selectOne 必须返回一个对象或 null 值。如果返回值多于一个,就会抛出异常。如果你不知道返回对象会有多少,请使用 selectList。如果需要查看某个对象是否存在,最好的办法是查询一个 count 值(0 或 1)。selectMap 稍微特殊一点,它会将返回对象的其中一个属性作为 key 值,将对象作为 value 值,从而将多个结果集转为 Map 类型值。由于并不是所有语句都需要参数,所以这些方法都具有一个不需要参数的重载形式。

事务控制方法#

有四个方法用来控制事务作用域。当然,如果你已经设置了自动提交或你使用了外部事务管理器,这些方法就没什么作用了。然而,如果你正在使用由 Connection 实例控制的 JDBC 事务管理器,那么这四个方法就会派上用场:

void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)

默认情况下 MyBatis 不会自动提交事务,除非它侦测到调用了插入、更新或删除方法改变了数据库。如果你没有使用这些方法提交修改,那么你可以在 commit 和 rollback 方法参数中传入 true 值,来保证事务被正常提交(注意,在自动提交模式或者使用了外部事务管理器的情况下,设置 force 值对 session 无效)。大部分情况下你无需调用 rollback(),因为 MyBatis 会在你没有调用 commit 时替你完成回滚操作。不过,当你要在一个可能多次提交或回滚的 session 中详细控制事务,回滚操作就派上用场了。

确保 SqlSession 被关闭#

void close()

对于你打开的任何 session,你都要保证它们被妥善关闭,这很重要。保证妥善关闭的最佳代码模式是这样的:

SqlSession session = sqlSessionFactory.openSession();
try (SqlSession session = sqlSessionFactory.openSession()) {
    // 假设下面三行代码是你的业务逻辑
    session.insert(...);
    session.update(...);
    session.delete(...);
    session.commit();
}

代理开发方式介绍

采用Mybatis 的代理开发方式实现 DAO 层的开发,这种方式是我们后面进入企业的主流。

Mapper 接口开发方法只需要程序员编写Mapper 接口(相当于Dao 接口),由Mybatis 框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。

Mapper 接口开发需要遵循以下规范:

1、Mapper.xml文件中的namespace与mapper接口的全限定名相同

2、Mapper接口方法名和Mapper.xml中定义的每个statement的id相同

3、Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同

4、Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同

snipaste20220402_134451.jpg

(修正了原课件中的错误)

Dao层(Mapper层)中只定义了接口

public interface UserMapper {

    public List<User> findAll() throws IOException;

    public User findById(int id);

}
public class ServiceDemo {

    public static void main(String[] args) throws IOException {

        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
	    // 获得MyBatis框架生成的UserMapper接口的实现类
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> all = mapper.findAll();
        System.out.println(all);

        User user = mapper.findById(1);
        System.out.println(user);

    }
}

动态SQL语句

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

if#

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:

<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符)。

如果希望通过 “title” 和 “author” 两个参数进行可选搜索该怎么办呢?

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

foreach#

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。

你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach

当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

循环执行sql的拼接操作,例如:SELECT * FROM USER WHERE id IN (1,2,5)

<select id="findByIds" parameterType="list" resultType="user"> 
    select * from User 
    <where> 
        <foreach collection="array" open="id in(" close=")" item="id" separator=","> 
            #{id}
        </foreach> 
    </where>
</select>

测试代码:

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

//模拟ids的数据
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);

List<User> userList = mapper.findByIds(ids);
System.out.println(userList);

out:

18:51:26,142 DEBUG JdbcTransaction:137 - Opening JDBC Connection
18:51:26,303 DEBUG PooledDataSource:406 - Created connection 959629210.
18:51:26,303 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3932c79a]
18:51:26,305 DEBUG findByIds:159 - ==>  Preparing: select * from user WHERE id in( ? , ? ) 
18:51:26,333 DEBUG findByIds:159 - ==> Parameters: 1(Integer), 2(Integer)
18:51:26,364 DEBUG findByIds:159 - <==      Total: 2
[User{id=1, username='tom', password='123456'}, User{id=2, username='jerry', password='123456'}]

foreach标签的属性含义如下: <foreach>标签用于遍历集合,它的属性:

  • collection:代表要遍历的集合元素,注意编写时不要写#{}
  • open:代表语句的开始部分
  • close:代表结束部分
  • item:代表遍历集合的每个元素,生成的变量名
  • sperator:代表分隔符

SQL语句抽取

可将重复的 sql 提取出来,使用时用 <include> 引用即可,最终达到 sql 重用的目的

<sql>标签:sql片段抽取

将上面的语句改成:

<!--sql语句抽取-->
<sql id="selectUser">select * from user</sql>

<select id="findByCondition" parameterType="user" resultType="user">
    <include refid="selectUser"></include>
    <where>
        <if test="id!=0">
            and id=#{id}
        </if>
        <if test="username!=null">
            and username=#{username}
        </if>
        <if test="password!=null">
            and password=#{password}
        </if>
    </where>
</select>

<select id="findByIds" parameterType="list" resultType="user">
    <include refid="selectUser"></include>
    <where>
        <foreach collection="list" open="id in(" close=")" item="id" separator=",">
            #{id}
        </foreach>
    </where>
</select>

类型处理器(typeHandlers)

官方文档:https://mybatis.org/mybatis-3/zh/configuration.html#typeHandlers

MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。

默认的类型处理器无需设置!

类型处理器 Java 类型 JDBC 类型
BooleanTypeHandler java.lang.Boolean, boolean 数据库兼容的 BOOLEAN
ByteTypeHandler java.lang.Byte, byte 数据库兼容的 NUMERICBYTE
ShortTypeHandler java.lang.Short, short 数据库兼容的 NUMERICSMALLINT
IntegerTypeHandler java.lang.Integer, int 数据库兼容的 NUMERICINTEGER
LongTypeHandler java.lang.Long, long 数据库兼容的 NUMERICBIGINT
FloatTypeHandler java.lang.Float, float 数据库兼容的 NUMERICFLOAT
DoubleTypeHandler java.lang.Double, double 数据库兼容的 NUMERICDOUBLE
BigDecimalTypeHandler java.math.BigDecimal 数据库兼容的 NUMERICDECIMAL
StringTypeHandler java.lang.String CHAR, VARCHAR
DateTypeHandler java.util.Date TIMESTAMP

提示 从 3.4.5 开始,MyBatis 默认支持 JSR-310(日期和时间 API) 。

你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。

具体做法为:实现org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个 JDBC 类型。

需求举例:

一个Java中的Date数据类型,我想将之存到数据库的时候存成一个1970年至今的毫秒数,取出来时转换成java的Date,即java的Date与数据库的毫秒值(bigint)之间转换。

开发步骤:

1、定义转换类继承类BaseTypeHandler<T> 2、覆盖4个未实现的方法,其中setNonNullParameter为java程序设置数据到数据库的回调方法,getNullableResult 为查询时mysql的字符串类型转换成 java的Type类型的方法 3、在MyBatis核心配置文件中进行注册 4、测试转换是否正确

public class DateTypeHandler extends BaseTypeHandler<Date> {
    // 将java类型转换成数据库需要的类型
    // int i表示表中第几个字段
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType jdbcType) throws SQLException {
        long time = date.getTime();
        preparedStatement.setLong(i, time);
    }

    // 将数据库中类型转换成java类型
    // String参数  要转换的字段名称
    // ResultSet 查询出的结果集
    public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
        // 获得结果集中需要的数据(long) 转换成Date类型 返回
        long aLong = resultSet.getLong(s);
        Date date = new Date(aLong);
        return date;
    }

    // 将数据库中类型转换成java类型
    public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
        long aLong = resultSet.getLong(i);
        Date date = new Date(aLong);
        return date;
    }

    // 将数据库中类型转换成java类型
    public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        long aLong = callableStatement.getLong(i);
        Date date = new Date(aLong);
        return date;
    }
}

public abstract long getLong(String columnLabel)方法

检索此ResultSet对象的当前行中指定列的值,作为Java编程语言中的long返回

sqlMapConfig.xml中注册自定义的类型处理器

<!--注册类型处理器-->
<typeHandlers>
	<typeHandler handler="com.itheima.handler.DateTypeHandler"></typeHandler>
</typeHandlers>

测试代码:

@Test
public void test1() throws IOException {
	... // 省略获取mapper
    //创建user
    User user = new User();
    user.setUsername("ceshi1");
    user.setPassword("abcdf");
    user.setBirthday(new Date());
    //执行保存操作
    mapper.save(user);

    sqlSession.commit();
    sqlSession.close();
}

数据库中的数据

snipaste20220402_134511.jpg

其中birthday字段以bigint方式存储

对刚插入的数据进行查询:

user中的birthday:Mon Mar 28 22:07:51 HKT 2022

返回的数据又被封装为Date类型

疑问:在表的设计中,birthday字段采用varchar和bigint都顺利完成了转换,为什么呢?

plugins标签(使用分页助手PageHelper)

MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即 可获得分页的相关数据

开发步骤: 1、导入通用PageHelper的坐标 2、在mybatis核心配置文件中配置PageHelper插件 3、测试分页数据获取

1、导入通用PageHelper的坐标

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>3.7.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>0.9.1</version>
</dependency>

2、在mybatis核心配置文件中配置PageHelper插件

<!--配置分页助手插件-->
<!-- 注意:分页助手的插件 配置在通用mapper之前 -->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageHelper">
        <!-- 指定方言 -->
        <property name="dialect" value="mysql"></property>
    </plugin>
</plugins>

3、测试分页代码实现

@Test
public void test3() throws IOException {
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    //设置分页相关参数   当前页+每页显示的条数
    PageHelper.startPage(3,2); // 每页显示两条数据,获取第3页的数据

    List<User> userList = mapper.findAll();
    for (User user : userList) {
        System.out.println(user);
    }

    //获得与分页相关参数
    PageInfo<User> pageInfo = new PageInfo<User>(userList);
    System.out.println("当前页:"+pageInfo.getPageNum());
    System.out.println("每页显示条数:"+pageInfo.getPageSize());
    System.out.println("总条数:"+pageInfo.getTotal());
    System.out.println("总页数:"+pageInfo.getPages());
    System.out.println("上一页:"+pageInfo.getPrePage());
    System.out.println("下一页:"+pageInfo.getNextPage());
    System.out.println("是否是第一个:"+pageInfo.isIsFirstPage());
    System.out.println("是否是最后一个:"+pageInfo.isIsLastPage());

    sqlSession.close();
}

一对一查询

一对一查询的模型#

用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户

一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户

snipaste20220402_134536.jpg

一对一查询的sql语句

sql查询语句(inner join):

select * from orders o,user u where o.uid=u.id;

查询结果:

snipaste20220402_134549.jpg

创建Order和User实体

在创建Order和User实体时,不使用主键/外键,而使用【面向对象】的思想。

public class Order {

    private int id;
    private Date ordertime;
    private double total;

    //当前订单属于哪一个用户
    private User user;
    
    // 省略set,get方法
}
public class User {

    private int id;
    private String username;
    private String password;
    private Date birthday;

    //描述的是当前用户存在哪些订单
    private List<Order> orderList;
    // 省略set,get方法
}

创建OrderMapper接口

public interface OrderMapper {

    //查询所有的订单(包含订单的用户信息)
    public List<Order> findAll();

}

配置OrderMapper.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.itheima.mapper.OrderMapper">

    <resultMap id="orderMap" type="order">
        <!--手动指定字段与实体属性的映射关系
            column: 数据表的字段名称
            property:实体的属性名称
        -->
        <id column="oid" property="id"></id>
        <result column="ordertime" property="ordertime"></result>
        <result column="total" property="total"></result>
        <!--
            property: 当前实体(order)中的属性名称(private User user)
            javaType: 当前实体(order)中的属性的类型(User)
        -->
        <association property="user" javaType="user">
            <id column="uid" property="id"></id>
            <result column="username" property="username"></result>
            <result column="password" property="password"></result>
            <result column="birthday" property="birthday"></result>
        </association>

    </resultMap>

    <select id="findAll" resultMap="orderMap">
         SELECT *,o.id oid FROM orders o,USER u WHERE o.uid=u.id
    </select>

</mapper>

测试代码:

public void test1() throws IOException {
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();

    OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
    List<Order> orderList = mapper.findAll();
    for (Order order : orderList) {
        System.out.println(order);
    }
    sqlSession.close();
}

out:

Order{id=1, ordertime=Mon Mar 28 00:00:00 HKT 2022, total=3000.0, user=User{id=1, username='tom', password='123456', birthday=null, roleList=null}}
Order{id=2, ordertime=Mon Mar 28 00:00:00 HKT 2022, total=5000.0, user=User{id=1, username='tom', password='123456', birthday=null, roleList=null}}
Order{id=3, ordertime=Sat Jan 01 00:00:00 HKT 2022, total=10000.0, user=User{id=2, username='jerry', password='123456', birthday=null, roleList=null}}
Order{id=4, ordertime=Wed Feb 02 00:00:00 HKT 2022, total=100.0, user=User{id=3, username='lucy', password='123', birthday=null, roleList=null}}

一对多查询

用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户

一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单

snipaste20220402_134522.jpg

一对一查询的sql语句#

sql查询语句(left join):

LEFT JOIN 关键字从左表(table1)返回所有的行,即使右表(table2)中没有匹配。如果右表中没有匹配,则结果为 NULL。

select *,o.id oid from user u left join orders o on u.id=o.uid;

查询结果:

snipaste20220402_134528.jpg

实体类沿用一对一查询中的User,Order

创建UserMapper接口:

public interface UserMapper {
    
    public List<User> findAll();
    
}

配置UserMapper.xml:

<resultMap id="userMap" type="user">
	<id column="id" property="id"></id>
	<result column="username" property="username"></result>
	<result column="password" property="password"></result>
	<result column="birthday" property="birthday"></result>
	<!--配置集合信息
		property:集合名称
		ofType:当前集合中的数据类型
	-->
	<collection property="orderList" ofType="order">
		<!--封装order的数据-->
		<id column="oid" property="id"></id>
		<result column="ordertime" property="ordertime"></result>
		<result column="total" property="total"></result>
	</collection>
</resultMap>

<select id="findAll" resultMap="userMap">
	SELECT *,o.id oid FROM USER u,orders o WHERE u.id=o.uid
</select>

注:已在sqlMaoConfig.xml中设置了实体类的别名,user的id不应该使用列uid,左连接会出现null

查询代码:

List<User> userList = mapper.findAll();

for (User user : userList) {
    List<Order> orderList = user.getOrderList();
    if (orderList.isEmpty()) {
        continue;
    }
    System.out.println(user.getUsername());
    for (Order order : orderList) {
        System.out.println(order);
    }
    System.out.println("----------------------------------");
}

out:

tom
Order{id=1, ordertime=Mon Mar 28 00:00:00 HKT 2022, total=3000.0, user=null}
Order{id=2, ordertime=Mon Mar 28 00:00:00 HKT 2022, total=5000.0, user=null}
----------------------------------
jerry
Order{id=3, ordertime=Sat Jan 01 00:00:00 HKT 2022, total=10000.0, user=null}
----------------------------------
lucy
Order{id=4, ordertime=Wed Feb 02 00:00:00 HKT 2022, total=100.0, user=null}

多对多查询

多对多查询的模型

用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用 多对多查询的需求:查询用户同时查询出该用户的所有角色

snipaste20220402_140006.jpg

多对多查询的语句对应的sql语句:

SELECT *
FROM USER u, sys_user_role ur, sys_role r
WHERE u.id = ur.userId
	AND ur.roleId = r.id;

查询的结果如下:

snipaste20220402_134554.jpg

创建Role实体,修改User实体

public class User {
	// ...
    //描述的是当前用户具备哪些角色
    private List<Role> roleList;
}
public class Role {

    private int id;
    private String roleName;
    private String roleDesc;
}

添加UserMapper接口方法

List<User> findAllUserAndRole();

配置UserMapper.xml:

<resultMap id="userRoleMap" type="user">
	<!--user的信息-->
	<id column="userId" property="id"></id>
	<result column="username" property="username"></result>
	<result column="password" property="password"></result>
	<result column="birthday" property="birthday"></result>
	<!--user内部的roleList信息-->
	<collection property="roleList" ofType="role">
		<id column="roleId" property="id"></id>
		<result column="roleName" property="roleName"></result>
		<result column="roleDesc" property="roleDesc"></result>
	</collection>
</resultMap>

<select id="findUserAndRoleAll" resultMap="userRoleMap">
	SELECT * FROM USER u,sys_user_role ur,sys_role r WHERE u.id=ur.userId AND ur.roleId=r.id
</select>

测试代码:

UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userAndRoleAll = mapper.findUserAndRoleAll();
for (User user : userAndRoleAll) {
    System.out.println(user);
}

out:

User{id=1, username='tom', password='123456', birthday=null, orderList=null, 
	roleList=[Role{id=1, roleName='院长', roleDesc='负责全面工作'}, 
			 Role{id=2, roleName='研究员', roleDesc='课程研发工作'}]}
User{id=2, username='jerry', password='123456', birthday=null, orderList=null, 
	roleList=[Role{id=2, roleName='研究员', roleDesc='课程研发工作'}, 
			 Role{id=3, roleName='讲师', roleDesc='授课工作'}]}

MyBatis多表配置方式:

  • 一对一配置:使用<resultMap>做配置
  • 一对多配置:使用<resultMap>+<collection>做配置
  • 多对多配置:使用<resultMap>+<collection>做配置

MyBatis的常用注解

@Insert:实现新增 @Update:实现更新 @Delete:实现删除 @Select:实现查询 @Result:实现结果集封装 @Results:可以与@Result 一起使用,封装多个结果集 @One:实现一对一结果集封装 @Many:实现一对多结果集封装

MyBatis的增删改查

在增删查改的测试代码部分,可以统一获取 UserMapper 对象

注意这里的@Before不是切面的注解,而是 junit 的注解。

@Before – 表示在任意使用@Test注解标注的public void方法执行之前执行

public class MyBatisTest {

    private UserMapper mapper;

    @Before
    public void before() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        mapper = sqlSession.getMapper(UserMapper.class);
    }

    @Test
    public void testSave() {
        User user = new User();
        user.setUsername("tom");
        user.setPassword("abc");
        mapper.save(user);
    }

    @Test
    public void testUpdate() {
        User user = new User();
        user.setId(18);
        user.setUsername("lucy");
        user.setPassword("123");
        mapper.update(user);
    }

    @Test
    public void testDelete() {
        mapper.delete(18);
    }

    @Test
    public void testFindById() {
        User user = mapper.findById(2);
        System.out.println(user);
    }

    @Test
    public void testFindAll() {
        List<User> all = mapper.findAll();
        for (User user : all) {
            System.out.println(user);
        }
    }
}

修改MyBatis的核心配置文件,因为我们使用了注解替代的映射文件,所以只需要加载使用了注解的Mapper接口即可。

<mappers> 
	<!--扫描使用注解的类--> 
	<mapper class="com.itheima.mapper.UserMapper"></mapper>
</mappers>

或者指定扫描包含映射关系的接口所在的包

<mappers> 
	<!--扫描使用注解的类所在的包--> 
	<package name="com.itheima.mapper"></package>
</mappers>

不再使用userMapper.xml

增删查改的注解实现(配置在userMapper接口中):

public interface UserMapper {
	// 插入
    @Insert("insert into user values(#{id},#{username},#{password},#{birthday})")
    public void save(User user);
	// 修改
    @Update("update user set username=#{username},password=#{password} where id=#{id}")
    public void update(User user);
	// 删除
    @Delete("delete from user where id=#{id}")
    public void delete(int id);
	// 查一条
    @Select("select * from user where id=#{id}")
    public User findById(int id);
	// 查列表
    @Select("select * from user")
    public List<User> findAll();
}

MyBatis的注解实现复杂映射开发

实现复杂关系映射之前我们可以在映射文件中通过配置<resultMap>来实现,使用注解开发后,可以使用@Results注解 ,@Result注解,@One注解,@Many注解组合完成复杂关系的配置

注解 说明
@Results 代替的是标签<resultMap>,该注解中可以使用单个@Result注解,也可以使用@Result集合。 使用格式:@Results({@Result(),@Result()})@Results(@Result())
@Result 代替了<id>标签和<result>标签 @Result中属性介绍: column:数据库的列名 property:需要装配的属性名 one:需要使用的@One注解 many:需要使用的@Many 注解
@One(一对一) 代替了<assocation> 标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。 @One注解属性介绍: select: 指定用来多表查询的 sqlmapper 使用格式:@Result(column=" ",property="",one=@One(select=""))
@Many(多对一) 代替了<collection>标签, 是是多表查询的关键,在注解中用来指定子查询返回对象集合。 使用格式:@Result(property="",column="",many=@Many(select=""))

一对一查询

查询一个订单,与此同时查询出该订单所属的用户

对应的sql语句(逻辑上不完全对应):

select * from orders;
select * from user where id=[查询出订单的uid];

使用注解配置OrderMapper接口

@Select("select * from orders")
@Results({
    @Result(column = "id", property = "id"),
    @Result(column = "ordertime", property = "ordertime"),
    @Result(column = "total", property = "total"),
    @Result(
        property = "user", //要封装的属性名称
        column = "uid", //根据哪个字段去查询user表的数据
        javaType = User.class, //要封装的实体类型
        one = @One(select = "com.itheima.mapper.UserMapper.findById") //select属性 代表查询那个接口的方法获得数据
    )
})
public List<Order> findAll();

snipaste20220402_134604.jpg只做一次查询的写法

@Select("select *,o.id oid from orders o,user u where o.uid=u.id")
@Results({
        @Result(column = "oid",property = "id"),
        @Result(column = "ordertime",property = "ordertime"),
        @Result(column = "total",property = "total"),
        @Result(column = "uid",property = "user.id"),
        @Result(column = "username",property = "user.username"),
        @Result(column = "password",property = "user.password")
})
public List<Order> findAll();

一对多查询

一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单

对应的sql语句:

select * from user;
select * from orders where uid=查询出用户的id;

使用注解配置UserMapper接口

@Select("select * from user")
@Results({
    @Result(id = true, column = "id", property = "id"),
    @Result(column = "username", property = "username"),
    @Result(column = "password", property = "password"),
    @Result(
        property = "orderList",
        column = "id",
        javaType = List.class,
        many = @Many(select = "com.itheima.mapper.OrderMapper.findByUid")
    )
})
public List<User> findUserAndOrderAll();

snipaste20220402_134614.jpg

多对多查询

多对多查询的需求:查询用户同时查询出该用户的所有角色

对应的sql语句:

select * from user; 
select * from role r,user_role ur where r.id=ur.role_id and ur.user_id=用户的id

使用注解配置RoleMapper接口

@Select("SELECT * FROM sys_user_role ur,sys_role r WHERE ur.roleId=r.id AND ur.userId=#{uid}")
public List<Role> findByUid(int uid);

使用注解配置UserMapper接口

@Select("select * from user")
@Results({
    @Result(id = true, column = "id", property = "id"),
    @Result(column = "username", property = "username"),
    @Result(column = "password", property = "password"),
    @Result(
        property = "roleList",
        column = "id",
        javaType = List.class,
        many = @Many(select = "com.itheima.mapper.RoleMapper.findByUid")
    )
})
public List<User> findUserAndRoleAll();

snipaste20220402_134621.jpg

MybatisTools

public class MybatisTools {
    private static  SqlSessionFactory factory;
    private MybatisTools(){}
    static {
        try {
            factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public  SqlSession getSession(){
        return factory.openSession(true);
    }

}
posted @   2hu0  阅读(47)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示
主题色彩