MyBatis_通用mapper注解开发

9. 通用mapper注解开发

除了XML配置开发的方式,mybatis还支持注解开发,并且为了提高开发效率,

通用Mapper都可以极大的方便开发人员。可以随意的按照自己的需要选择通用方法,还可以很方便的开发自己的通用方法。

极其方便的使用MyBatis单表的增删改查。

支持单表操作,不支持通用的多表联合查询。

通用 Mapper 支持 Mybatis-3.2.4 及以上版本。

github地址:https://gitee.com/free/Mapper

码云官方文档:https://gitee.com/free/Mapper/wikis/Home

9.1. 通过mapper入门案例

使用通用mapper完成数据库的读写操作

  1. 添加通用mapper的依赖包
  2. 配置mybatis-config.xml
  3. 集成通用mapper功能
  4. 准备表、类数据读写
  5. 编写mapper接口
  6. 测试:使用通用mapper读写数据
9.1.1. 添加mapper依赖
<!-- https://mvnrepository.com/artifact/tk.mybatis/mapper -->
<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper</artifactId>
    <version>4.1.5</version>
</dependency>

其他依赖

<dependencies>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.5</version>
</dependency>

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.18</version>
</dependency>


<!-- junit测试 -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
    <scope>test</scope>
</dependency>

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

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.10</version>
    <scope>test</scope>
</dependency>

</dependencies>

<build>
        <resources>
           <!-- 打包src/main/java 下面的属性文件和xml文件-->
            <!-- 在进行模块化开放打jar包时,maven会将非Java文件过滤掉,
            xml,properties配置文件等,但是这些文件又是必需的,
            使用此配置可以在打包时将不会过滤这些必需的配置文件-->
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <!--是否替换资源中的属性-->
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>
9.1.2. 相关配置文件
- jdbc.properties
//#注意mybatis版本>=6.0使用如下驱动,如果<6.0使用com.mysql.jdbc.Driver
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&&serverTimezone=Hongkong&useSSL=false
username=root
password=root
- mybatis-config.xml
<?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 resource="jdbc.properties"></properties>

    <!--typeAliases配置别名-->
    <typeAliases>
        <!--扫描包下所有类,取别名,别名默认是类名的小写字母-->
        <package name="com.czxy.mybatis.model"/>
    </typeAliases>
    
    <environments default="development">
        <environment id="development">
            <!--
             transactionManager:配置事务管理器
             在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
             JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施
             MANAGED – 这个配置几乎没做什么
             -->
            <transactionManager type="JDBC"/>
            <!--
            dataSource: 配置数据源
            type可选值:UNPOOLED 不使用连接池
                      POOLED使用连接池
                      JNDI 从配置好的JNDI数据源获取连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--映射器-->
    <mappers>
        <!--扫描接口-->
        <package name="com.czxy.mybatis.mapper"/>
    </mappers>
</configuration>
- log4j.properties
### 设置###
# debug 日志级别,常用的4个日志级别:ERROR、WARN、 INFO、DEBUG
log4j.rootLogger = debug,stdout,D,E

### 输出信息到控制抬 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

### 输出DEBUG 级别以上的日志到=E://logs/error.log ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = E://logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG 
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

### 输出ERROR 级别以上的日志到=E://logs/error.log ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =E://logs/error.log 
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR 
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n
9.13. 集成通用mapper
  • MyBatisMapperUtils
package com.czxy.mybatis.util;

import java.io.IOException;
import java.io.Reader;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import tk.mybatis.mapper.entity.Config;
import tk.mybatis.mapper.mapperhelper.MapperHelper;

public class MyBatisMapperUtils {

    private static SqlSessionFactory sqlSessionFactory = null;
    /*
     * 使用ThreadLocal为每一个线程独立管理一个session对象 每一个线程只有且仅有单独且唯一的一个session对象
     * 使用ThreadLocal对session进行管理,可以保证线程安全,避免多实例同时调用同一个session对象
     * 每一个线程都会独立管理自己的session对象
     */
    private static ThreadLocal<SqlSession> threadlocal = new ThreadLocal<SqlSession>();

    // 创建sessionFactory对象,因为整个应用程序只需要一个实例工厂
    // 而静态代码块随着类的加载而执行,而且只执行一次,因此放入静态代码块中初始化工厂实例
    static {
        try {
            //读取配置文件
            Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
            //创建会话工厂
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
            //关闭读取流
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 新建session会话,并把session放在线程变量中
     */
    private static void newSession() {
        //从刚刚创建的 sqlSessionFactory 中获取 session
        SqlSession session = sqlSessionFactory.openSession();

        //创建一个MapperHelper
        MapperHelper mapperHelper = new MapperHelper();
        //配置 mapperHelper 后,对Configuration进行加工
        mapperHelper.processConfiguration(session.getConfiguration());

        // 将session会话保存在本线程变量中
        threadlocal.set(session);
    }

    /**
     * 返回session对象
     * @return session
     */
    public static SqlSession getSession(){
        //优先从线程变量中取session对象
        SqlSession session = threadlocal.get();
        //如果线程变量中的session为null,
        if(session==null){
            //新建session会话,并把session放在线程变量中
            newSession();
            //再次从线程变量中取session对象
            session = threadlocal.get();
        }
        return session;
    }

    /**
     * 关闭session对象,并从线程变量中删除
     */
    public static void closeSession(){
        //读取出线程变量中session对象
        SqlSession session = threadlocal.get();
        //如果session对象不为空,关闭sessoin对象,并清空线程变量
        if(session!=null){
            session.close();
            threadlocal.set(null);
        }
    }

    /**
     * 提交并关闭资源
     */
    public static void commitAndclose() {

        //获取连接
        SqlSession openSession = getSession();
        //非空判断
        if(openSession!=null) {
            //提交
            openSession.commit();
            //关闭资源
            openSession.close();
            //从threadlocal中移除session
            threadlocal.remove();
        }
    }
}

实现原理

这种配置方式是通过重写原 Configuration 中的 addMappedStatement 方法来实现的:

@Override
public void addMappedStatement(MappedStatement ms) {
  try {
    super.addMappedStatement(ms);
    //没有任何配置时,使用默认配置
    if (this.mapperHelper == null) {
      this.mapperHelper = new MapperHelper();
    }
    this.mapperHelper.processMappedStatement(ms);
  } catch (IllegalArgumentException e) {
    //这里的异常是导致 Spring 启动死循环的关键位置,为了避免后续会吞异常,这里直接输出
    e.printStackTrace();
    throw new RuntimeException(e);
  }
}
9.1.4. 添加表和类
品牌表brands
DROP TABLE IF EXISTS `brands`;
CREATE TABLE `brands` (
  `brand_id` INT NOT NULL AUTO_INCREMENT COMMENT '品牌ID',
  `brand_name` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT 'none' COMMENT '品牌名称',
  `brand_img` VARCHAR(150) DEFAULT NULL COMMENT '品牌图标',
  `create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
  `data_flag` TINYINT DEFAULT NULL COMMENT '删除标志 -1:删除 1:有效',
  PRIMARY KEY (`brand_id`)
) ENGINE=INNODB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8

insert  into `brands`(`brand_id`,`brand_name`,`brand_img`,`create_time`,`data_flag`) values (1,'瑞士莲','/img/lindt_chocolate.img','2020-01-18 16:32:17',-1),(2,'德芙','/img/dove.img','2020-03-18 09:12:57',1),(3,'费列罗','/img/FERRERO_ROCHER.img','2020-07-06 15:36:19',1),(4,'怡口莲','/img/CADBURY.img','2020-07-13 18:32:51',-1),(5,'百草味','/img/BCW.img','2020-07-20 09:53:26',1),(6,'百草之味','/img/xfj.img','2020-05-22 11:32:17',1);

实体类Brands

这里给productId加上了@ID注解,表示这个属性对应表中的主键列,具体原因后续在介绍注解时会详细介绍

public class Brands {
    @Id
    private Integer brandId;    // 品牌ID
    private String brandName;   // 品牌名称
    private String brandImg;    // 品牌图标
    private Date createTime;    // 创建时间
    private Character dataFlag; //'删除标志 -1:删除 1:有效',
    
    //get set toString()...
}
9.1.5. 接口继承通用mapper
//继承通用Mapper接口
//Mapper中的泛型用于绑定查询结果对应的实体类型
public interface BrandsMapper extends Mapper<Brands> {

}
9.1.6. 测试
/**
  * 测试通用mapper
  */
@Test
public void hello(){
    //1. 获取session连接
    SqlSession session = MyBatisMapperUtils.getSession();

    //2. 获取mapper接口
    BrandsMapper mapper = session.getMapper(BrandsMapper.class);

    //3. 调用通用mapper的方法
    Brands brand = mapper.selectByPrimaryKey(1);
    System.out.println(brand);
    //关闭连接
    MyBatisMapperUtils.closeSession();
}

9.2. ORM

  • 通用mapper的自动关系映射

    通用 Mapper 中,默认情况下是将实体类字段按照驼峰转下划线形式的表名列名进行转换

    例如

    实体类的 userName 可以映射到表的 user_name 上。

因此我们在mybatis-config.xml中并未配置开启驼峰命名规则转换,通用mapper已经开启了。

如果数据库中的字段名和实体类的字段名是完全相同的,这中情况下实体和表可以直接映射。

  • 手动绑定映射关系

    通用 Mapper 使用 JPA 注解和自己提供的注解来实现对象关系映射.

    JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML,用以描述对象和关系表的映射关系,并将运行期的实体对象持久化到数据库中。

9.2.1. @Table (JPA)

配置类和表直接的关系,如果表名和类名一致或者符合驼峰命名规则则可以省略不写

//将 User 实体映射到 sys_user 表。
@Table(name = "sys_user")
public class User
9.2.2. @Id (JPA)

@Id 注解和映射无关,它是一个特殊的标记,用于标识数据库中的主键字段。

正常情况下,一个实体类中至少需要一个标记 @Id 注解的字段,存在联合主键时可以标记多个。

如果表中没有主键,类中就可以不标记。

当类中没有存在标记 @Id 注解的字段时,你可以理解为类中的所有字段是联合主键。使用所有的 ByPrimaryKey 相关的方法时,有 where 条件的地方,会将所有列作为条件。

配置示例:

@Id
private Integer id;

或者联合主键:

@Id
private String phone;
@Id
private Integer productID;
9.2.3. 主键策略

主键策略就是用来获取生成的主键ID

获取主键的注解

  • @KeySql (mapper)

    这是通用 Mapper 的自定义注解,改注解的目的就是替换 @GeneratedValue 注解。推荐使用keysql

  • @GeneratedValue(JPA)

    主键策略注解,用于配置如何生成主键。

新增的@KeySql 注解用于替换 @GeneratedValue 注解,因此 @KeySql 能以更简单方式实现原来的功能,下面的示例都先使用 @KeySql 进行配置,然后在使用 @GeneratedValue,大家可以自己选择。

需要数据库支持自增,其次数据库提供的 JDBC 支持 getGeneratedKeys 方法。

常见的如 MySql,SqlServer 支持这种模式。

  • 用法如下:
@Id
@KeySql(useGeneratedKeys = true)
private Long id;

​ 或:

@Id
@GeneratedValue(generator = "JDBC")
private Long id;

  • 为了让大家容易理解这里配置和 MyBatis 写法的关系,大家可以看看对应生成的 XML 代码:
<!--
useGeneratedKeys:是否使用JDBC方式获取主键,使用getGeneratedKeys 方法获取主键
keyProperty:主键对应属性
-->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
    insert into country (id, countryname, countrycode)
    values (#{id},#{countryname},#{countrycode})
</insert>
9.2.4. @Column (JPA)

配置映射的列名,如果列名和属性名一致或者符合驼峰命名规则则可以省略不写

@Column(name = "user_name")
private String name;

在使用关键字的情况,还会有下面的用法:

@Column(name = "`order`")
private String order;
9.2.5. @ColumnType (mapper)

这个注解提供的 column属性和 @Column 中的 name 作用相同。但是 @Column 的优先级更高。

除了 name 属性外,这个注解主要提供了 jdbcType 属性和 typeHandler 属性。

jdbcType 用于设置特殊数据库类型时指定数据库中的 jdbcType

typeHandler 用于设置特殊类型处理器,常见的是枚举。

用法示例如下:

@ColumnType(
        column = "countryname",
        jdbcType = JdbcType.VARCHAR,
        typeHandler = StringTypeHandler.class)
private String  countryname;
9.2.6. @Transient (JPA)

一般情况下,实体中的字段和数据库表中的字段是一一对应的,但是也有很多情况我们会在实体中增加一些额外的属性,这种情况下,就需要使用 @Transient 注解来告诉通用 Mapper 这不是表中的字段。否则通用mapper会自动映射表中的列,因为表中没有这个列,会抛出异常

@Transient
private String otherThings; //非数据库表中字段

9.2. 通用mapper

9.2.1. 常用方法
  • 查询方法
方法名 描述
T selectOne(T t) 根据实体中的属性进行查询,只能有一个返回值,有多个结果是抛出异常,查询条件使用等号
List select(T t) 根据实体中的属性值进行查询,查询条件使用等号
List selectAll() 查询全部结果
int selectCount(T t) 根据实体中的属性查询总数,查询条件使用等号
T selectByPrimaryKey(Object key) 根据主键字段进行查询
boolean existsWhithPrimaryKey(Object key) 根据主键字段查询记录是否存在
List selectByExample(Object example) 根据Example条件进行查询
T selectOneByExample(Object example) 根据Example条件进行查询,只能有一个返回值
int selectCountByExample(Object example) 根据Example条件进行查询记录数
  • 插入方法
方法名 描述
int insert(T t) 保存一个实体,null的属性也会保存,不会使用数据库默认值
int intsertSelective(T t) 保存一个实体,null的属性不会保存,使用数据库默认值
  • 更新方法
方法名 描述
int updateByPrimaryKey(T t) 根据主键更新实体全部字段,null值会被更新
int updateByPrimaryKeySelective(T t) 根据主键更新实体中不为null值的字段
  • 删除方法
方法名 描述
int delete(T t) 根据实体属性作为条件进行删除,查询条件使用等号
int deletePrimaryKey(Object key) 根据主键字段进行删除
int deleteByExample(Object example) 根据Example条件删除数据
9.2.2. 通用Example

这是由通用 Mapper 提供的一个类,需要自己设置属性名。这个类提供了很多条件查询的方法。

可以帮我们完成sql语句中where条件句的书写,相当于where后面的部分,我们可以根据不同的条件来查询

1. 查询

需求:查询ID > 1 并且 小于等于 3 或者ID 大于5的品牌,结果按照品牌ID降序排序

/**
  * Example查询
  */
@Test
public void exampleSelect(){
    //1. 获取session连接
    SqlSession session = MyBatisMapperUtils.getSession();

    //2. 获取mapper接口
    BrandMapper mapper = session.getMapper(BrandMapper.class);

    //创建通用Example
    //example用于设置关键字条件 or and order by
    Example example = new Example(Brands.class);
    //创建criteria,用于设置属性条件: 属性 > < between like
    Example.Criteria criteria = example.createCriteria();
    criteria.andGreaterThan("brandId",1).andLessThanOrEqualTo("brandId",3);
    //example.or() 另起一个条件
    example.or().andGreaterThan("brandId",5);
    //根据ID降序排序
    example.orderBy("brandId").desc();

    //使用example查询
    List<Brands> brands = mapper.selectByExample(example);

    //关闭链接
    MyBatisMapperUtils.closeSession();
}

打印的日志:

==>  Preparing: SELECT brand_id,brand_name,brand_img FROM brands WHERE ( ( brand_id > ? and brand_id <= ? ) or ( brand_id > ? ) ) order by brand_id DESC

==> Parameters: 1(Integer), 3(Integer), 5(Integer)

<==      Total: 3
2. 动态SQL

​ 案例:用户输入的查询条件数量不确定,需要判断参数是否为空,进行动态SQL拼接
在这里插入图片描述

需求:根据品牌名称模糊查询,根据创建日期进行范围查询

​ 示例:

/**
  * Example查询
  * 根据品牌名称模糊查询,根据创建日期进行范围查询
  */
@Test
public void dynamicSQL() throws ParseException {
    //1. 获取session连接
    SqlSession session = MyBatisMapperUtils.getSession();

    //2. 获取mapper接口
    BrandMapper mapper = session.getMapper(BrandMapper.class);

    //3. 模拟用户输入的参数
    Brands brands = new Brands();
    //品牌名称
    brands.setBrandName("%百草%");
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
    Date startTime = format.parse("2020-05-01");
    Date endTime = format.parse("2020-11-01");
    //创建的开始时间
    brands.setCreateStartTime(startTime);
    //创建的结束时间
    brands.setCreateEndTime(endTime);

    //使用Example查询
    Example example = new Example(Brands.class);
    Example.Criteria criteria = example.createCriteria();
    //动态拼接SQL
    if(brands.getBrandName()!= null && !brands.getBrandName().equals("")){
        //根据品牌名称模糊查询
        criteria.andLike("brandName",brands.getBrandName());
    }
    //开始日期
    if(brands.getCreateStartTime() != null){
        criteria.andGreaterThanOrEqualTo("createTime",brands.getCreateStartTime());
    }
    //结束日期
    if (brands.getCreateEndTime() != null){
        criteria.andLessThanOrEqualTo("createTime",brands.getCreateEndTime());
    }

    List<Brands> brandsList = mapper.selectByExample(example);

    //关闭链接
    MyBatisMapperUtils.closeSession();
}
3. 设置查询列

查询部分列

需求:查询商品ID、商品名称、删除标志

/**
  * Example查询
  * 设置查询列
  */
@Test
public void exampleColumn(){
    //1. 获取session连接
    SqlSession session = MyBatisMapperUtils.getSession();

    //2. 获取mapper接口
    BrandMapper mapper = session.getMapper(BrandMapper.class);

    //创建通用Example
    //example用于设置关键字条件 or and order by
    Example example = new Example(Brands.class);
    example.selectProperties("brandId","brandName","dataFlag");

    //使用example查询
    List<Brands> brands = mapper.selectByExample(example);

    //关闭链接
    MyBatisMapperUtils.closeSession();
}
4. 行级锁
/**
  * Example查询
  * forupdate 行级锁
  */
@Test
public void exampleForupdate(){
    //1. 获取session连接
    SqlSession session = MyBatisMapperUtils.getSession();

    //2. 获取mapper接口
    BrandMapper mapper = session.getMapper(BrandMapper.class);

    //创建通用Example
    //example用于设置关键字条件 or and order by
    Example example = new Example(Brands.class);
    //开启行级锁
    example.setForUpdate(true);
    //查询条件
    example.createCriteria().andEqualTo("brandId",6);

    //使用example查询,查询执行到链接关闭之前,该条数据不会释放锁
    //其他人修改不了该数据
    List<Brands> brands = mapper.selectByExample(example);

    //关闭链接
    MyBatisMapperUtils.closeSession();
}

for update是一种行级锁,又叫排它锁,一旦用户对某个行施加了行级加锁,则该用户可以查询也可以更新被加锁的数据行,其它用户只能查询但不能更新被加锁的数据行.如果其它用户想更新该表中的数据行,则也必须对该表施加行级锁.即使多个用户对一个表均使用了共享更新,但也不允许两个事务同时对一个表进行更新,真正对表进行更新时,是以独占方式锁表,一直到提交或复原该事务为止。行锁永远是独占方式锁。

只有当出现如下之一的条件,才会释放共享更新锁:

1、执行提交(COMMIT)语句

2、退出数据库

3、程序停止运行

9.2.3. 自定义方法(注解方式)

如果通用mapper或者Example满足不了需求,或者不想用以上2中方式,那么可以自定义方法执行SQL,但是需要注意,在对应的 XML 中,不能出现和继承接口中同名的方法!

使用注解自定义SQL查询,通用mapper不会进行驼峰规则映射,只会将属性和列名全转大写,如果相同则进行关系映射。因此需要在mybatis-config.xml中开启驼峰命名规则转换

<settings>
    <!--开启驼峰规则映射-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

接口方法

package com.czxy.mybatis.mapper;

import com.czxy.mybatis.model.Brands;
import org.apache.ibatis.annotations.Select;
import tk.mybatis.mapper.common.Mapper;

public interface BrandsMapper extends Mapper<Brands> {

    @Select("SELECT * FROM brands WHERE brand_id = #{bid}")
    public Brands selectBrandById(Integer brandid);
}

测试:

/**
  * 自定义注解方法
  */
@Test
public void selectBrandById(){
    //1. 获取session连接
    SqlSession session = MyBatisMapperUtils.getSession();

    //2. 获取mapper接口
    BrandMapper mapper = session.getMapper(BrandMapper.class);

    Brands brands = mapper.selectBrandById(5);
    System.out.println(brands);

    //关闭链接
    MyBatisMapperUtils.closeSession();
}

到此大家应该发现,使用接口注解方式添加方法,关键就是以下几点:

  1. 编写SQL
  2. 传递参数给SQL语句
  3. SQL语句接收参数
  4. 接收返回值
1. 参数传递
  • 单个参数接收和之前XML方式一样

    • 接口方法直接传参
    • SQL中使用#{变量名} 或者${变量名}接收,名称随意,因为就一个参数,不会取错
  • 多个参数传递

    • 使用多个变量传递参数:@Param(“变量名”) ,指定将参数传递给SQL中的哪个变量
    • 使用Map传递参数:key 必须和变量名一致
    • 使用pojo对象传参
  • 准备商品表和商品类

    商品表

CREATE TABLE `product` (
  `product_id` VARCHAR(32) NOT NULL COMMENT '商品ID',
  `product_img` VARCHAR(50) DEFAULT NULL COMMENT '商品图片',
  `product_name` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '商品名称',
  `product_sale` INT DEFAULT NULL COMMENT '商品销量',
  `stock` INT DEFAULT NULL COMMENT '商品库存',
  `product_price` DOUBLE DEFAULT NULL COMMENT '商品价格',
  `brandid` INT DEFAULT NULL COMMENT '品牌ID',
  PRIMARY KEY (`product_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

insert  into `product`(`product_id`,`product_img`,`product_name`,`product_sale`,`stock`,`product_price`,`brandid`) values ('p01','/img/dove/213231.img','Dove/德芙丝滑牛奶巧克力252g碗装排块糖果糖巧休闲零食品',1800062,2000000,29.9,2),('p02','/img/dove/352342.img','德芙麦提莎麦丽素麦芽脆夹心巧克力豆520g/桶装休闲零食糖果',209710,300000,79.9,2),('p03','/img/dove/635987.img','Dove/德芙丝滑牛奶巧克力252g*2碗装排块休闲零食品糖果',344410,500000,59.8,2),('p04','/img/rocher/845127.img','费列罗榛果巧克力48粒送女友零食婚庆礼盒3*16条喜糖糖果礼',563315,1000000,139,3),('p05','/img/rocher/345311.img','费列罗拉斐尔椰蓉扁桃仁糖果酥球10粒 休闲零食',19460,25000,79.9,3);

商品类

public class Product {

    @Id
    private String productId;
    private String productImg;
    private String productName;
    private Integer productSale;
    private Integer stock;
    private Double productPrice;
    private Integer brandid;
    //get set toString()
}
  • 使用多个变量传递参数

    案例: 查询商品价格高于79,并且销量大于200000的商品

package com.czxy.mybatis.mapper;

import com.czxy.mybatis.model.Product;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import tk.mybatis.mapper.common.Mapper;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

public interface ProductMapper extends Mapper<Product> {

    /**
     * 使用多个变量传递参数
     * 必须使用@Param指定赋值给哪个变量
     * @param price
     * @param sale
     * @return
     */
    @Select("SELECT * FROM product  WHERE product_price > #{productPrice} AND product_sale > #{productSale}")
    public List<Product> listProductByPriceAndSale(@Param("productPrice") BigDecimal price,@Param("productSale") Integer sale);

测试:

/**
     * 自定义注解方法
     * 传递多个参数
     */
@Test
public void listProductByPriceAndSale(){
    //1. 获取session连接
    SqlSession session = MyBatisMapperUtils.getSession();

    //2. 获取mapper接口
    ProductMapper mapper = session.getMapper(ProductMapper.class);
    //3. 传递多个参数查询
    List<Product> products = mapper.listProductByPriceAndSale(new BigDecimal(79), 200000);

    //关闭链接
    MyBatisMapperUtils.closeSession();
}
  • 使用Map传递参数

    接口方法

/**
     * 使用Map传递多个参数
     * map的key必须和变量名一致
     * @param map
     * @return
     */
@Select("SELECT * FROM product WHERE product_price > #{productPrice} AND product_sale > #{productSale}")
public List<Product> listProductByPriceAndSale_map(Map<String,Object> map);

测试:

/**
     * 自定义注解方法
     * 使用map传递多个参数
     */
@Test
public void listProductByPriceAndSale_map(){
    //1. 获取session连接
    SqlSession session = MyBatisMapperUtils.getSession();

    //2. 获取mapper接口
    ProductMapper mapper = session.getMapper(ProductMapper.class);
    HashMap<String, Object> map = new HashMap<>();
    //map的key必须和变量名一致
    map.put("productPrice",79.0);
    map.put("productSale",200000);
    List<Product> products = mapper.listProductByPriceAndSale_map(map);

    //关闭链接
    MyBatisMapperUtils.closeSession();
}
  • 使用pojo传递多个参数

    pojo中拥有变量名对应的get方法,如果没有会报错,mybatis会调用对应的get方法获取参数

    #{productPrice} 对应 getproductPrice()方法

/**
     * 使用pojo传递多个参数
     * pojo中拥有变量名对应的get方法,如果没有会报错
     * mybatis会调用对应的get方法获取参数
     * @param product
     * @return
     */
@Select("SELECT * FROM product WHERE product_price > #{productPrice} AND product_sale > #{productSale}")
public List<Product> listProductByPriceAndSale_pojo(Product product);

测试:

/**
     * 自定义注解方法
     * 使用pojo类传递多个参数
     */
@Test
public void listProductByPriceAndSale_pojo(){
    //1. 获取session连接
    SqlSession session = MyBatisMapperUtils.getSession();

    //2. 获取mapper接口
    ProductMapper mapper = session.getMapper(ProductMapper.class);
    HashMap<String, Object> map = new HashMap<>();

    //使用pojo类对象传递参数
    Product product = new Product();
    product.setProductPrice(new BigDecimal(79.0));
    product.setProductSale(200000);

    List<Product> products = mapper.listProductByPriceAndSale_pojo(product);

    //关闭链接
    MyBatisMapperUtils.closeSession();
}
2. 返回值接收

常用接收SQL执行结果的方式是map 和pojo类

  • map示例:
@Select("SELECT * FROM brands WHERE brand_id = #{bid}")
public Map<String,Object> selectBrandById(Integer brandid);
  • pojo接收

    符合以下3中规则的,mybatis会自动将列的值封装到类属性中

  1. 类属性名(忽略大小写)和列名称相同
  2. 符合驼峰名称规则,并且开启驼峰命名规则转换
  3. 手动设置了@Results绑定属性和列的关系
  • 之前xml配置结果映射的方式
<resultMap id="productMap" type="product">
    <id property="productId" column="product_id" javaType="int" jdbcType="INTEGER"></id>
    <result property="productName" column="product_name" ></result>
    <result property="productImg" column="product_img" ></result>
</resultMap>
  • 现在使用注解方式
/**
     * 使用resultMap手动绑定映射关系
     * @param product
     * @return
     */
@Select("SELECT * FROM product WHERE product_price > #{productPrice} AND product_sale > #{productSale}")
@Results(id = "productMap",value = {
    //id : 标识这是主键
    //javaType: 指定属性的类型,类名.class 可以省略不写,mybatis会自动推导
    //JdbcType: 列类型,使用枚举JdbcType获取,可以省略不写,mybatis会自动推导
    @Result(property = "productId",column = "product_id",id = true,javaType = String.class,jdbcType = JdbcType.VARCHAR),
    @Result(property = "productImg",column = "product_img")
    //其他属性省略... 自己写吧
})
public List<Product> listProductByPriceAndSale_results(Product product);
  • @ResultMap
    @ResultMap的用法。当这段@Results代码需要在多个方法用到时,为了提高代码复用性,我们可以为这个@Results注解设置id,然后使用@ResultMap注解来复用这段代码。
@Select("select * from product")
@ResultMap("productMap")
public List<Product> listProducts();
3. 多对一查询

注解方式使用的是嵌套select查询实现多表查询

需求:根据订单ID查询订单时,关联展示用户名和手机号

  • 环境准备:Orders User表和实体类(mybatis XML多表查询已准备)

  • 基于注解实现多对一查询

  1. 编写SQL
  2. 将SQL修改为注解方式
  3. 通过result添加嵌套查询
  4. 测试

User类
在这里插入图片描述
OrderMapper

package com.czxy.mybatis.mapper;

import com.czxy.mybatis.model.Orders;
import org.apache.ibatis.annotations.*;
import tk.mybatis.mapper.common.Mapper;
import java.util.List;

public interface OrderMapper extends Mapper<Orders> {

    /**
     * 多对一查询
     * 需求:查询用户时,将该用户所有的订单查询出来
     * @return
     */
    @Select("select * from orders")
    @Results(id = "ordersMap",value = {
            @Result(property = "orderId",column = "order_id",id = true),
            @Result(property = "userId",column = "user_id"),
            //one: 表示不是集合属性,也就是对一的关系
            //select: 关联执行的查询方法,全限定类名加方法名
            @Result(property = "users",column = "user_id",
                    one = @One(select = "com.czxy.mybatis.mapper.UserMapper.selectByPrimaryKey"))
    })
    public List<Orders> selectOrderByID();
}
  • 测试
package com.czxy.Test;

import com.czxy.mybatis.mapper.OrderMapper;
import com.czxy.mybatis.mapper.UserMapper;
import com.czxy.mybatis.model.Orders;
import com.czxy.mybatis.model.User;
import com.czxy.mybatis.util.MyBatisMapperUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;

public class OrderTest {

    /**
    * 使用多对一查询
     * 需求:查询用户时,将该用户所有的订单查询出来
    */
    @Test
    public void listOrders(){
        //1获取session连接
        SqlSession session = MyBatisMapperUtils.getSession();
        //2获取mapper 连接
        OrderMapper mapper = session.getMapper(OrderMapper.class);
        //3调用接口
        List<Orders> orders = mapper.selectOrderByID();

        for (Orders order : orders) {
            System.out.println(order);
        }
        //4关闭连接
        MyBatisMapperUtils.closeSession();
    }
}
4. 一对多查询

需求:查询用户时,将该用户所有的订单查询出来

  1. 编写SQL语句
  2. 将SQL语句改写成注解方式
  3. 编写results,通过嵌套select进行关联查询

订单类
在这里插入图片描述

  • OrderMapper
import com.czxy.mybatis.model.Orders;
import org.apache.ibatis.annotations.*;
import tk.mybatis.mapper.common.Mapper;
import java.util.List;

public interface OrderMapper extends Mapper<Orders> {
/**
     * 一对多查询
     * 根据用户id查询订单
     * **/
    @Select("select * from orders where user_id=#{uid}")
    //@ResultMap 复用 ordersMap
    //@ResultMap("ordersMap")
    public List<Orders>ListOrdersByUID(Integer uid);
}
  • UserMapper
package com.czxy.mybatis.mapper;

import com.czxy.mybatis.model.User;
import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import tk.mybatis.mapper.common.Mapper;

public interface UserMapper extends Mapper<User> {

    /***
     * 使用注解方式实现一对多查询
     * 一个用户对应多个订单
     * */
    @Select("SELECT * FROM USER WHERE uid=#{uid}")
    @Results(id = "userResultMap",value = {
            @Result(property = "uid",column = "uid",id = true),
            @Result(property = "orders",column = "uid",many = @Many(select = "com.czxy.mybatis.mapper.OrderMapper.ListOrdersByUID"))
    })
    public User selectOneUser(Integer uid);
}
  • 测试
package com.czxy.Test;

import com.czxy.mybatis.mapper.OrderMapper;
import com.czxy.mybatis.mapper.UserMapper;
import com.czxy.mybatis.model.Orders;
import com.czxy.mybatis.model.User;
import com.czxy.mybatis.util.MyBatisMapperUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;

public class OrderTest {
/**
     * 使用注解方式实现一对多查询
     * **/
    @Test
    public void listUserAndOrders(){
        //1获取session连接
        SqlSession session = MyBatisMapperUtils.getSession();
        //2获取mapper 连接
        UserMapper mapper = session.getMapper(UserMapper.class);
        //3调用接口
        User user = mapper.selectOneUser(6);
        System.out.println(user);

        //4关闭连接
        MyBatisMapperUtils.closeSession();
    }
}
9.2.4. 自定义方法(XML方式)

使用xml自定义方法,要求xml文件和接口同名同包
在这里插入图片描述

  • UserMapper.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必须是接口全限定名-->
<mapper namespace="com.czxy.mybatis.mapper.UserMapper">
    <!--使用xml式实现自定义查询-->
    <select id="selectUserByID" resultType="user">
        select * from user where uid=#{uid}
    </select>
</mapper>
  • UserMapper
public interface UserMapper extends Mapper<User> {
    /**
     * 使用xml式实现自定义查询
     * */
    public User selectUserByID(Integer uid);
}
  • 测试
public class OrderTest {
    /**
     * 使用xml式实现自定义查询
     * **/
    @Test
    public void selectByXML(){
        //1获取session连接
        SqlSession session = MyBatisMapperUtils.getSession();
        //2获取mapper 连接
        UserMapper mapper = session.getMapper(UserMapper.class);
        //3调用接口
        User user = mapper.selectUserByID(6);
        System.out.println(user);
        //4关闭连接
        MyBatisMapperUtils.closeSession();
    }
}

猜你喜欢

转载自blog.csdn.net/qq_44509920/article/details/107599466