Mybatis框架

Mybatis的主要作用快速实现对关系型数据库中的数据进行访问的框架在原生的Java技术中,需要使用JDBC实现对数据库中的数据访问,执行过程繁琐且相对固定,使用框架可以有效地提高开发效率

创建Mybatis-Spring工程

Mybatis可以不依赖于Spring等框架直接使用的,但是,就需要进行大量的配置,前期配置工作量较大,基于Spring框架目前是业内使用的标准之一,所以,通常会整合Spring与Mybatis,以减少配置在创建工程时,创建普通的Maven工程即可不需要选择特定的骨架

在pom.xml中添加几个依赖项

//Mybatis的依赖项:mybatis
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
//Mybatis整合Spring的依赖项:mybatis-spring
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
//Spring的依赖项:spring-context
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.14</version>
</dependency>
 //Spring JDBC的依赖项:spring-jdbc
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.14</version>
</dependency>
//MySQL连接的依赖项:mysql-connector-java
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
 //数据库连接池的依赖项:commons-dbcp2
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.8.0</version>
</dependency>
//JUnit测试的依赖项:junit-jupiter-api
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>

创建完成后,可以在src/test/java下创建测试类,并编写测试方法,例如:

package cn.tedu.mybatis;
import org.junit.jupiter.api.Test;
public class MybatisTests {
  @Test
  public void contextLoads() {
    System.out.println("MybatisTests.contextLoads()");
  }
}
//由于目前尚未编写实质的代码,以上测试代码也非常简单,应该是可以成功通过测试的

配置Mybatis的开发环境

在src/main/resources下创建datasource.properties配置文件,用于配置连接数据库的参数,例如:

datasource.url=jdbc:mysql://localhost:3306/mall_ams?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
datasource.driver=com.mysql.cj.jdbc.Driver
datasource.username=root
datasource.password=root

在cn.tedu.mybatis包下(不存在,则创建)创建SpringConfig类,读取以上配置文件:

@Configuration
@PropertySource("classpath:datasource.properties")
public class SpringConfig {
}
//@PropertySource是Spring框架的注解,用于读取properties类
//型的配置文件,读取到的值将存入到Spring容器的Environment对象中

在SpringConfig中配置一个DataSource对象

@Configuration
@PropertySource("classpath:datasource.properties")
public class SpringConfig {
  @Bean
  public DataSource dataSource(Environment env) {
    BasicDataSource dataSource = new BasicDataSource();
    dataSource.setUrl(env.getProperty("datasource.url"));
    dataSource.setDriverClassName(env.getProperty("datasource.driver"));
    dataSource.setUsername(env.getProperty("datasource.username"));
    dataSource.setPassword(env.getProperty("datasource.password"));
    return dataSource;
  }
}

Mybatis的基本使用

当使用Mybatis实现数据访问时,主要:

  1. – 编写数据访问的抽象方法
  2. – 配置抽象方法对应的SQL语句

关于抽象方法:

  • 必须定义在某个接口中,这样的接口通常使用Mapper作为名称的后缀,例如AdminMapper
  • Mybatis框架底层将通过接口代理模式来实现方法的返回值类型:如果要执行的数据操作是增、删、改类型的,统一使用int作为返回值类型,表示“受影响的行数”,也可以使用void,但是不推荐如果要执行的是查询操作,返回值类型只需要能够装载所需的数据即可

方法的名称:自定义,不要重载,建议风格如下:

  • – 插入数据使用`insert`作为方法名称中的前缀或关键字
  • – 删除数据使用`delete`作为方法名称中的前缀或关键字
  • – 更新数据使用`update`作为方法名称中的前缀或关键字
  • – 查询数据时:如下:
  1. 如果是统计,使用`count`作为方法名称中的前缀或关键字
  2. 如果是单个数据,使用`get`或`find`作为方法名称中的前缀或关键字
如果是列表,使用`list`作为方法名称中的前缀或关键字
  1. 如果操作数据时有条件,可在以上前缀或关键字右侧添加`by字段名`,例如`deleteById`

方法的参数列表:取决于需要执行的SQL语句中有哪些参数,如果有多个参数,可将这些参数封装到同一个类型中,使用封装的类型作为方法的参数类型

在cn.tedu.mybatis包下创建mapper.AdminMapper接口,并在接口中添加“插入1条管理员数据”的抽象方法:

package cn.tedu.mybatis.mapper;
import cn.tedu.mybatis.Admin;
public interface AdminMapper {
  int insert(Admin admin);
}

所有用于Mybatis处理数据的接口都必须被Mybatis识别,有2种做法:

  1. – 在每个接口上添加@Mapper注解
  2. – 【推荐】在配置类上添加@MapperScan注解,指定接口所在的根包

在SpringConfig上添加配置@MapperScan:

@Configuration
@PropertySource("classpath:datasource.properties")
@MapperScan("cn.tedu.mybatis.mapper")
public class SpringConfig {
  // ... ...
}
//注意:因为Mybatis会扫描以上配置的包,并自动生成包中各接口中的代
//理对象,所以,千万不要放其它接口文件

XML Mapper数据查询

先在datasource.properties中补充一条配置:

mybatis.mapper-locations=classpath:mapper/*Mapper.xml
//然后在配置类中创建SqlSessionFactoryBean类型的对象*/
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource,
@Value("${mybatis.mapper-locations}") Resource mapperLocations) {
  SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
  sqlSessionFactoryBean.setDataSource(dataSource);
  sqlSessionFactoryBean.setMapperLocations(mapperLocations);
  return sqlSessionFactoryBean;
}
<?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属性用于配置此XML对应哪个接口 -->
<mapper namespace="cn.tedu.mybatis.mapper.AdminMapper">
<!-- 根据需要执行的SQL语句的种类选择需要配置的节点名称 -->
<!-- 配置SQL的节点的id属性取值为抽象方法名称 -->
<!-- 在节点内部配置SQL语句 -->
<!-- SQL语句中的参数值使用 #{} 格式的占位符表示 -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
   insert into ams_admin (username, password, nickname, avatar,
      phone, email, description, is_enable,
      last_login_ip, login_count, gmt_last_login,
      gmt_create, gmt_modified)
values (#{username}, #{password}, #{nickname}, #{avatar},
   #{phone}, #{email}, #{description}, #{isEnable},
   #{lastLoginIp}, #{loginCount}, #{gmtLastLogin},
   #{gmtCreate}, #{gmtModified})
</insert>

<select id="getByUsername" resultMap="BaseResultMap">
  select
	<include refid="BaseQueryFields" />
  from
ams_admin
where
username=#{username}
</select>

<sql id="BaseQueryFields">
  <if test="true">
    id,
    username,
    password,
    nickname,
    avatar,
    phone,
    email,
    description,
    is_enable,
    last_login_ip,
    login_count,
    gmt_last_login,
    gmt_create,
    gmt_modified
</if>
</sql>

<resultMap id="BaseResultMap" type="cn.tedu.boot.demo.entity.Admin">
  <id column="id" property="id" />
  <result column="username" property="username" />
  <result column="password" property="password" />
  <result column="nickname" property="nickname" />
  <result column="avatar" property="avatar" />
  <result column="phone" property="phone" />
  <result column="email" property="email" />
  <result column="description" property="description" />
  <result column="is_enable" property="isEnable" />
  <result column="last_login_ip" property="lastLoginIp" />
  <result column="login_count" property="loginCount" />
  <result column="gmt_last_login" property="gmtLastLogin" />
  <result column="gmt_create" property="gmtCreate" />
  <result column="gmt_modified" property="gmtModified" />
 </resultMap>

</mapper>

获取新增的数据的自动编号的id

在<insert>节点配置2个属性,分别是useGeneratedKeys和keyProperty:

<insert id="insert" useGeneratedKeys="true" keyProperty="id">
  //原有代码
</insert>
//Mybatis执行此插入数据的操作后,会将自动编号的id赋
//值到参数Admin admin的id属性中,以上keyProperty指的就是将自动编
//号的值放回到参数对象的哪个属性中

修改数据

目标:根据id修改某一条数据的密码,要实现此目标,需要执行的SQL语句大致是:

update ams_admin set password=? where id=?

在AdminMapper接口中添加抽象方法:

int updatePasswordById(@Param("id") Long id,@Param("password") String password);

【注】当Java程序源代码(.java文件)经过编译后,所有局部的量的名称都会丢失,为使得配置SQL语句时可根据指定的名称使用方法中的参数值,需要在方法的各参数前添加@Param以指定名称

– 如果方法的参数只有1个,则可以不使用@Param指定名称,因为Mybatis可以直接找到此参数的值

<update id="updatePasswordById">
update ams_admin set password=#{password} where id=#{id}
</update>

查询数据--统计

在AdminMapper接口中添加抽象方法:

int count();

在AdminMapper.xml中配置以上抽象方法映射的SQL语句:

<!-- int count(); -->
<select id="count" resultType="int">
select count(*) from ams_admin
</select>
//注意:所有select节点必须配置resultType或resultMap这2个属性中的其中1个             

查询某1条记录

<select id="getById" resultMap="BaseResultMap">
select * from ams_admin where id=#{id}
</select>
<!-- resultMap节点的作用是:指导Mybatis如何将结果集中的数据封装到返回的对象中 -->
<!-- id属性:自定义名称 -->
<!-- type属性:将结果集封装到哪种类型的对象中 -->
<resultMap id="BaseResultMap" type="cn.tedu.mybatis.Admin">
<!-- 使用若干个result节点配置名称不统一的对应关系 -->
<!-- 在单表查询中,名称本来就一致的是不需要配置的 -->
<!-- column属性:列名 -->
<!-- property属性:属性名 -->
<result column="is_enable" property="isEnable" />
<result column="last_login_ip" property="lastLoginIp" />
<result column="login_count" property="loginCount" />
<result column="gmt_last_login" property="gmtLastLogin" />
<result column="gmt_create" property="gmtCreate" />
<result column="gmt_modified" property="gmtModified" />
</resultMap>

查询列表

在AdminMapper接口中添加抽象方法

List<Admin> list();

在AdminMapper.xml中配置以上抽象方法映射的SQL语句:

<!-- List<Admin> list(); -->
<select id="list" resultMap="BaseResultMap">
select * from ams_admin order by id
</select>
//(1) 查询时,结果集中可能超过1条数据时,必须显式的使用ORDER BY子句对结果集进行排序;
//(2) 查询时,结果集中可能超过1条数据时,应该考虑是否需要分页

动态SQL

Mybatis中的动态SQL表现为:根据参数不同,生成不同的SQL语句例如,你可以对某些参数值进行判断,根据判断结果走向不同分支,来决定SQL语句的某个片段,如果参数值是可遍历的,你还可以遍历此参数来生成部分SQL片段

sql - foreach

//AdminMapper.xml中配置以上抽象方法映射的SQL语句
<!-- int deleteByIds(List<Long> ids); -->
<delete id="deleteByIds">
delete from ams_admin where id in 
<foreach collection="list" item="id" separator=",">
#{id}
</foreach>
)
</delete>
// separator属性:分隔符号,会自动添加在遍历到的各元素之间 in (1,2,3)

sql - if

<select id="selectByUser" resultType="">
  SELECT id,
      user_name,
      user_password,
      user_email,
      create_time
  FROM sys_user
  WHERE 1 = 1
  <if test="userName != null and userName != ''">
    AND user_name LIKE CONCAT('%',#{userName},'%')
  </if>
  <if test="userEmail != null and userEmail != ''">
    AND user_email = #{userEmail}
  </if>
</select>

关联查询

RBAC = Role Based Access Control(基于角色的访问控制)

RBAC是经典的用户权限管理的设计思路。在这样的设计中,会存在3种类型:用户、角色、权限,权限将分配到各种角色上,用户可以关联某种角色,进而实现用户与权限相关。使用这样的设计,更加利于统一管理若干个用户的权限。

在RBAC的设计思路中,用户与角色一般是多对多的关系,而在数据库中,仅仅只是使用“用户”和“角色”这2张表是不利于维护多对多关系的,通常会增加一张中间表,专门记录对应关系,同理,角色和权限也是多对多的关系,也需要使用中间表来记录对应关系!

<!-- AdminDetailsVO getDetailsById(Long id); -->
<select id="getDetailsById" resultMap="DetailsResultMap">
  select
<include refid="DetailsQueryFields"/>
  from ams_admin
  left join ams_admin_role on ams_admin.id = ams_admin_role.admin_id
left join ams_role on ams_role.id = ams_admin_role.role_id
where ams_admin.id=#{id}
</select>

<sql id="DetailsQueryFields">
  <if test="true">
    ams_admin.id,
    ams_admin.username,
    ams_admin.password,
    ams_admin.nickname,
    ams_admin.avatar,
    ams_admin.phone,
    ams_admin.email,
    ams_admin.description,
    ams_admin.is_enable,
    ams_admin.last_login_ip,
    ams_admin.login_count,
    ams_admin.gmt_last_login,
    ams_admin.gmt_create,
    ams_admin.gmt_modified,

    ams_role.id AS role_id,
    ams_role.name AS role_name,
    ams_role.description AS role_description,
    ams_role.sort AS role_sort,
    ams_role.gmt_create AS role_gmt_create,
    ams_role.gmt_modified AS role_gmt_create
     </if>
</sql>

    <!-- resultMap节点的作用:指导mybatis将查询到的结果集封装到对象中 -->
    <!-- resultMap节点的id属性:自定义名称 -->
    <!-- resultMap节点的type属性:封装查询结果的类型的全限定名 -->
    <!-- 主键应该使用id节点进行配置,非主键、非集合的使用result节点进行配置 -->
    <!-- column=结果集中的列名,property=属性名 -->
    <!-- 在关联查询中,即便结果集中的列名与类的属性名完全相同,也必须配置 -->
    <!-- collection子节点:用于配置1对多关系的数据部分,通常在类中是List类型的属性 -->
    <!-- collection子节点的ofType:List集合中的元素的类型 -->
    <resultMap id="DetailsResultMap" type="cn.tedu.mybatis.vo.AdminDetailsVO">
        <id column="id" property="id" />
        <result column="username" property="username" />
        <result column="password" property="password" />
        <result column="nickname" property="nickname" />
        <result column="avatar" property="avatar" />
        <result column="phone" property="phone" />
        <result column="email" property="email" />
        <result column="description" property="description" />
        <result column="is_enable" property="isEnable" />
        <result column="last_login_ip" property="lastLoginIp" />
        <result column="login_count" property="loginCount" />
        <result column="gmt_last_login" property="gmtLastLogin" />
        <result column="gmt_create" property="gmtCreate" />
        <result column="gmt_modified" property="gmtModified" />
        <collection property="roles" ofType="cn.tedu.mybatis.entity.Role">
            <id column="role_id" property="id" />
            <result column="role_name" property="name" />
            <result column="role_description" property="description" />
            <result column="role_sort" property="sort" />
            <result column="role_gmt_create" property="gmtCreate" />
            <result column="role_gmt_modified" property="gmtModified" />
        </collection>
    </resultMap>

Mybatis小结(需要掌握的知识点

关于Spring MVC框架,你应该:

  • 了解如何创建一个整合了Spring框架的Mybatis工程
  • 了解整合了Spring框架的Mybatis工程的配置
  • 掌握声明抽象方法的原则:
  • 返回值类型:增删改类型的操作均返回int,表示“受影响的行数”,查询类型操作,根据操作得到的结果集来决定,只要能够放入所有所需的数据即可,通常,统计查询返回int,查询最多1个结果时返回自定义的某个数据类型,查询多个结果时返回List集合类型
  • 方法名称:自定义,不要使用重载,其它命名建议参考此前的笔记
  • 参数列表:根据需要执行的SQL语句中的参数来设计,并且,当需要执行的是插入数据操作时,必须将这些参数进行封装,并在封装的类中添加主键属性,以便于Mybatis能获取自动编号的值回填到此主键属性中,当需要执行的是其它类型的操作时,如果参数数量较多,可以封装,如果只有1个,则直接声明为方法参数,如果超过1个且数量不多,则每个参数之前添加@Param注解
  • 了解使用注解配置SQL语句
  • 掌握使用XML配置SQL语句
  1. – 这类XML文件需要顶部特殊的声明,所以,通常是从网上下载或通过复制粘贴得到此类文件
  2. – 根节点必须是<mapper>,且必须配置namespace,取值为对应的Java接口的全限定名
  3. – 应该根据要执行的SQL语句不同来选择<insert>、<delete>、<update>、<select>节点,这些节点都必须配置id属性,取值为对应的抽象方法的名称
  4. – 其实,<delete>节点和<update>节点可以随意替换使用,但不推荐
  5. – 在不考虑“获取自动生成的主键值”的情况下,<insert>和<delete>、<update>也可以混为一谈,但不推荐
  6. – 当插入数据时,当需要获取自动生成的主键值时,需要在<insert>节点上配置useGeneratedKeys和keyProperty属性
  7. – 在<select>节点上,必须配置resultMap或resultType属性中的其中1个
  • 掌握使用<sql>封装SQL语句片段,并使用<include>进行引用,以实现SQL语句的复用
  • – 掌握<resultMap>的配置方式
  1. – 主键列与属性的映射必须使用<id>节点配置
  2. – 在1对多、多对多的查询中,集合类型的属性的映射必须使用<collection>子节点配置
  3. – 其它列与属性的映射使用<result>节点配置
  4. – 在单表查询中,列与属性名一致时,可以不必显式的配置,但是,在关联查询中,即使列与属性名称一致,也必须显式的配置出来
  • – 理解resultType与resultMap使用原则
  • – 尽可能的全部使用resultMap,如果查询结果是单一某个数据类型(例如基本数据类型或字符串或某个时间等),则使用resultType
  • – 掌握动态SQL中的`<foreach>`的使用

关于#{}和${}格式的占位符

使用#{}格式的占位符时,Mybatis在处理时会使用预编译的做法,所以,在编写SQL语句时不必关心数据类型的问题(例如字符串值不需要添加单引号),也不存在SQL注入的风险!这种占位符只能用于表示某个值,而不能表示SQL语句片段!

使用${}格式的占位符时,Mybatis在处理时会先将参数值代入到SQL语句中,然后再执行编译相关过程,所以需要关心某些值的数据类型问题(例如涉及字符串值时,需要在编写SQL语句时添加一对单引号框住字符串),并且,存在SQL注入的风险!其优点是可以表示SQL语句中的任何片段!

Mybatis的缓存机制

缓存:通常是一个临时存储的数据,在未来的某个时间点可能会被删除

  1. 通常,存储缓存数据的位置是读写效率较高的,相比其它“非缓存”的数据有更高的处理效率
  2. 由于缓存的数据通常并不是必须的,则需要额外消耗一定的存储空间,同时由于从缓存获取数据的效率更高,所以是一种牺牲空间、换取时间的做法
  3. 另外,你必须知道,从数据库读取数据的效率是非常低下的

Mybatis有2种缓存机制,分别称之一级缓存和二级缓存

一级缓存是基于SqlSession的缓存,也称之为“会话缓存”,仅当是同一个会话、同一个Mapper、同一个抽象方法(同一个SQL语句)、同样的参数值时有效,一级缓存在集成框架的应用中默认是开启的,且整个过程不由人为控制(如果是自行得到SqlSession后的操作,可自行清理一级缓存)

二级缓存默认是全局开启的,它是基于namespace的,所以也称之为“namespace缓存”,需要在配置SQL语句的XML中添加<cache />节点,以表示当前XML中的所有查询都允许开通二级缓存,并且,在<select>节点上配置useCache="true",则对应的<select>节点的查询结果将被二级缓存处理,并且,此查询返回的结果的类型必须是实现了Serializable接口的,如果使用了<resultMap>配置如何封装查询结果,则必须使用<id>节点来封装主键的映射,满足以上条件后,二级缓存将可用,只要是当前namespace中查询出来的结果,都会根据所执行的SQL语句及参数进行结果的缓存

无论是一级缓存还是二级缓存,只要数据发生了写操作(增、删、改),缓存数据都将被自动清理

【注】Mybatis的缓存清理机制过于死板,所以,一般在开发实践中并不怎么使用!更多的是使用其它的缓存工具并自行制定缓存策略

学习记录,如有侵权请联系删除

举报
评论 0