Data Access 之 MyBatis(八)- MyBatis 通用 Mapper(Part A)

2022-08-19 17:14:02 浏览数 (1)

通用Mapper都可以极大的方便开发人员。可以随意的按照自己的需要选择通用方法,还可以很方便的开发自己的通用方法。极其方便的使用MyBatis单表的增删改查。

那么已经有了MyBatis Generator可以生成SQL语句,为什么还需要通用Mapper?

使用MyBatis Generator生成器生成的Entity实体类和Mapper接口及Mapper XML文件是一一对应的,SQL语句也是根据实体类的属性生成的。随着需求的变化,可能需要对某些Entity实体类增加或者删除一些属性,那么实体类对应的Mapper XML也就需要进行手动修改,这就会导致一系列的错误

而使用通用Mapper就避免了实体类属性变化的时候同时需要手动修改Mapper XML文件,通用Mapper会自动根据实体类的属性生成响应的SQL,不需要再生成Mapper XML就可以实现基本的增删改查以及查询

一、搭建框架 - Mybatis整合Spring

在数据库创建两张表分别是porsche和t_teacher

代码语言:javascript复制
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for porsche
-- ----------------------------
DROP TABLE IF EXISTS `porsche`;
CREATE TABLE `porsche` (
  `por_id` int(11) NOT NULL AUTO_INCREMENT,
  `por_name` char(100) CHARACTER SET gb2312 COLLATE gb2312_chinese_ci DEFAULT NULL,
  `por_price` double DEFAULT NULL,
  `por_stock` int(11) DEFAULT NULL,
  PRIMARY KEY (`por_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=gb2312;

-- ----------------------------
-- Records of porsche
-- ----------------------------
BEGIN;
INSERT INTO `porsche` VALUES (1, 'Panamera', 970000, 90);
INSERT INTO `porsche` VALUES (2, 'Cayenne', 910000, 100);
INSERT INTO `porsche` VALUES (3, 'Macan', 550000, 80);
INSERT INTO `porsche` VALUES (4, 'Taycay 2022', 880000, 50);
INSERT INTO `porsche` VALUES (5, 'Porsche 911', 1270000, 40);
INSERT INTO `porsche` VALUES (6, 'Porsche 718', 540000, 70);
INSERT INTO `porsche` VALUES (7, '918 Spyder', 13380000, 10);
INSERT INTO `porsche` VALUES (8, 'Cayman', 720000, 30);
INSERT INTO `porsche` VALUES (9, 'Boxster', 670000, 110);
INSERT INTO `porsche` VALUES (10, 'Carrera GT', 6450000, 20);
INSERT INTO `porsche` VALUES (11, 'Taycan Tubo S', 880000, 140);
INSERT INTO `porsche` VALUES (12, 'Taycan 2023', 880000, 120);
COMMIT;

DROP TABLE IF EXISTS `t_teacher`;
CREATE TABLE `t_teacher` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `teacher_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `class_name` varchar(255) DEFAULT NULL,
  `address` varchar(255) DEFAULT NULL,
  `birth_date` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=58 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of t_teacher
-- ----------------------------
BEGIN;
INSERT INTO `t_teacher` VALUES (1, 'stark', '三年二班', '浦东', '2022-02-13');
INSERT INTO `t_teacher` VALUES (2, 'steve', '三年三班', '静安', '2022-02-12');
INSERT INTO `t_teacher` VALUES (3, 'clint', '三年四班', '黄埔', '2022-02-11');
INSERT INTO `t_teacher` VALUES (4, 'banner', '三年二班', '静安', '2022-02-16');
INSERT INTO `t_teacher` VALUES (5, 'thor', '三年四班', '浦东', '2022-02-16');
INSERT INTO `t_teacher` VALUES (6, 'strange', '三年三班', '黄埔', '2022-02-16');

SET FOREIGN_KEY_CHECKS = 1;

新建一个maven项目common-mapper,在pom.xml中添加Spring和MyBatis以及数据库驱动的相关依赖

代码语言:javascript复制
<properties>
    <spring-version>5.3.13</spring-version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    <!--spring jdbc依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring-version}</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.16</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.14</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>

    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.18</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.7</version>
    </dependency>

</dependencies>

在resources目录下新建Spring配置文件application.xml,并整合MyBatis

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-4.3.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.alibaba.com/schema/stat http://www.alibaba.com/schema/stat.xsd http://www.springframework.org/schema/tool http://www.springframework.org/schema/tool/spring-tool.xsd">

    <!--包扫描配置-->
    <context:component-scan base-package="com.citi">
    </context:component-scan>
    <!--引用外部配置文件-->
    <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>

    <!--数据库连接池配置-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc_driver}"/>
        <property name="url" value="${jdbc_url}" />
        <property name="username" value="${jdbc_username}"/>
        <property name="password" value="${jdbc_password}"/>
        <property name="initialSize" value="${jdbc_initialSize}"/>
        <property name="maxActive" value="${jdbc_maxActive}"/>
    </bean>

    <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
        <property name="dataSource" ref="dataSource"></property>
        <property name="mapperLocations" value="classpath:mappers/*.xml"></property>
    </bean>

    <!--Dao层接口加入到Spring容器-->
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定接口所在的包-->
        <property name="basePackage" value="com.citi.mapper"></property>
    </bean>
    
    <!--事务管理器配置-->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--开启基于注解的配置模式-->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager" />

</beans>

在resource目录下新建MyBatis配置文件mybatis-config.xml,只需要settings标签即可。

代码语言:javascript复制
<?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>

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>

在resources目录下创建数据库连接信息配置文件db.properties

代码语言:javascript复制
jdbc_driver=com.mysql.cj.jdbc.Driver
jdbc_url=jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf8&amp;autoReconnect=true&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai
jdbc_username=root
jdbc_password=root
jdbc_initialSize=10
jdbc_maxActive=20

resources目录下创建日志配置,用于在控制台打印出执行的SQL

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
       <encoder>
           <pattern>[%thread] %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
       </encoder>
   </appender>
    <root level="debug">
        <appender-ref ref="console"/>
    </root>
</configuration>

新建entity包,增加实体类Porsche和Teacher

代码语言:javascript复制
@Data
public class Porsche {

    private Integer porId;
    private String porName;
    private Double porPrice;
    private Integer porStock;
}
代码语言:javascript复制
@Data
public class Teacher {

    private Integer id;
    private String name;
    // 执教的年级
    private String grade;
    private String address;
    private Date birthDate;
}

新增mapper包,增加PorscheMapper接口和TeacherMapper接口

代码语言:javascript复制
public interface PorscheMapper {

    Porsche getPorscheById();
}
代码语言:javascript复制
public interface TeacherMapper {
}

在resources目录下创建mappers文件夹,新增PorscheMapper.xml

代码语言:javascript复制
<?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.citi.mapper.PorscheMapper">

    <select id="getPorscheById" resultType="com.citi.entity.Porsche">
        select * from porsche where por_id = #{id}
    </select>
</mapper>

在test包下新建测试类PorscheMapperTest

代码语言:javascript复制
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application.xml")
public class PorscheMapperTest {

    @Resource
    private PorscheMapper porscheMapper;

    @Test
    public void getPorscheById(){
        Porsche panamera = porscheMapper.getPorscheById(1);
        System.out.println("查询到的内容为:"   panamera);
    }
}

执行测试

控制台成功查询出id为1的Porsche对象,框架搭建成功

二、通用Mapper实现基本增删改查-BaseMapper

2.1 集成通用Mapper

首先在pom.xml中通用Mapper的依赖

代码语言:javascript复制
<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper</artifactId>
    <version>4.1.5</version>
</dependency>

接着需要修改Spring配置文件

代码语言:javascript复制
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--其他内容不变-->
    <!--Mapper XML的配置注销,使用通用Mapper就不再需要Mapper XML了-->
    <!--<property name="mapperLocations" value="classpath:mappers/*.xml"></property>-->
</bean>

<!--Dao层接口加入到Spring容器, 使用tk包下面的MapperScannerConfigurer,原来是org开头-->
<bean id="mapperScannerConfigurer" class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
    <!--其他内容不变-->
    <property name="basePackage" value="com.citi.mapper"></property>
</bean>

删除mapper文件夹下的PorscheMapper.xml,因为使用通用Mapper不需要Mapper XML文件

2.2 selectOne () 方法

修改PorscheMapper接口及TeacherMapper接口,需要继承通用Mapper提供的Mapper接口,泛型为实体类类型,删除原有的方法

代码语言:javascript复制
public interface TeacherMapper extends Mapper<Teacher> {
}
代码语言:javascript复制
public interface PorscheMapper extends Mapper<Porsche> {
}

因为需要在声明式事务中调用Service层,新建service及impl两个包,增加PorscheServic接口PorscheServiceImpl实现类

代码语言:javascript复制
public interface PorscheService {

    Porsche getOne(Porsche record);
}
代码语言:javascript复制
@Service
public class PorscheServiceImpl implements PorscheService {

    @Autowired
    private PorscheMapper porscheMapper;

    @Override
    public Porsche getOne(Porsche record) {

        return porscheMapper.selectOne(record);
    }
}

查看看通用Mapper源码

  • BaseMapper:基本操作
  • ExampleMapper:Example封装查询条件,实现QBC查询,QBC(Query By Criteria)查询
  • RowBoundsMapper是做分页查询的

新建PorscheService接口的测试类

代码语言:javascript复制
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application.xml")
public class PorscheServiceTest {

    @Autowired
    private PorscheService porscheService;

    @Test
    public void getOne() {
        // 构造查询条件
        Porsche record = new Porsche();
        record.setPorName("Cayenne");
        record.setPorPrice(910000d);
        // 执行查询
        Porsche porsche = porscheService.getOne(record);
        System.out.println("查询到的内容为:"   porsche);
    }
}

执行测试

根据控制台输出的SQL语句可以看出设置的查询条件全部生效,继承的通用Mapper的方法可用。

实体类封装查询条件的规则

  • 使用非空的值生成WHERE子句
  • 在条件表达式中使用 “=” 进行比较 如果根据查询条件返回多个结果则会报异常,将PorscheServiceTest中getOne方法中的set语句注释掉,再次执行测试

selectOne要求只返回一个结果

2.3 @Table和@Column注解

由于通用Mapper根据实体类生成对应的SQL语句,这就要求实体类的类名和属性最好与数据库表名和字段一致,如果不一致则会导致通用Mapper中提供的方法无法使用,以Teacher实体类和t_teacher表为例。

service包中新建TeacherService接口和TeacherServiceImpl实现类,新增getOne方法,调用TeacherMapper的selectOne方法

代码语言:javascript复制
public interface TeacherService {
    
    Teacher getOne(Teacher record);
}
代码语言:javascript复制
@Service
public class TeacherServiceImpl implements TeacherService {

    @Autowired
    private TeacherMapper teacherMapper;

    @Override
    public Teacher getOne(Teacher record) {
        return teacherMapper.selectOne(record);
    }
}

test包下增加TeacherService接口的测试类TeacherServiceTest

代码语言:javascript复制
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application.xml")
public class TeacherServiceTest {

    @Autowired
    private TeacherService teacherService;

    @Test
    public void getOne() {
        // 构造查询条件
        Teacher record = new Teacher();
        record.setGrade("三年二班");
        record.setName("stark");
        Teacher teacher = teacherService.getOne(record);
        System.out.println(teacher);
    }
}

执行测试

控制台输出SQL执行报错信息,可以看到执行的SQL语句中查询的表与字段与数据库中的t_teacher不一致。

通用Mapper可以通过在类名上增加@Table注解使类名和数据库表名保持一致,@Column注解可以使实体类属性和数据库字段名保持一致,使用这两个注解更改Teacher实体类

代码语言:javascript复制
@Data
@Table(name = "t_teacher")
public class Teacher {

    private Integer id;
    @Column(name = "teacher_name")
    private String name;
    // 执教的年级
    @Column(name = "class_name")
    private String grade;
    private String address;
    private Date birthDate;
}

再次执行测试

控制台成功输出grade属性为三年二班name为stark的Teacher对象。

plus:实体类属性birthDate与表字段birth_date可以通过mybatis-config.xml中的settings标签配置开启驼峰命名转换解决。

2.4 select 相关方法与 @Id 注解

代码语言:javascript复制
// 根据实体中的属性值进行查询,返回List列表,查询条件使用等号
List<T> select(T record);

// 查询全部结果,返回List列表
List<T> selectAll();

// 根据实体中的属性查询总数,返回int类型,查询条件使用等号
int selectCount(T record);

// 根据主键字段进行查询,方法参数必须包含完整的主键属性,返回一个实体类,查询条件使用等号

T selectByPrimaryKey(Object key);

// 根据主键字段查询总数,方法参数必须包含完整的主键属性,返回True或者False查询条件使用等号

boolean existsWithPrimaryKey(Object key);
2.4.1 测试 selectByPrimaryKey 方法

在PorscheService和PorscheServiceImpl中定义和实现方法getPorscheById

代码语言:javascript复制
Porsche getPorscheById(Integer id);
代码语言:javascript复制
@Override
public Porsche getPorscheById(Integer id) {
    return porscheMapper.selectByPrimaryKey(id);
}

在PorscheServiceTest类中增加getPorscheById测试方法

代码语言:javascript复制
@Test
public void getPorscheById(){
    Porsche porsche = porscheService.getPorscheById(2);
    System.out.println("查询到的内容为:"   porsche);
}

执行测试

控制台输出查询的内容为null,并且执行的SQL语句中WHERE子句后面跟了全部属性作为条件,跟预想的以por_id作为查询条件不同。这是因为实体类中属性中没有显示的标注哪个属性对应数据库中的主键,通用Mapper把所有的字段集合起来当成了一个联合主键

在Porsche实体类上的por_id属性上增加@Id注解,再次执行测试

数据库表的主键与实体类中的por_id属性对应起来,成功查询出数据

2.4.2 测试 existsWithPrimaryKey 方法

在PorscheService和PorscheServiceImpl中定义和实现方法isExists

代码语言:javascript复制
Boolean isExists(Integer id);
代码语言:javascript复制
@Override
public Boolean isExists(Integer id) {
    return porscheMapper.existsWithPrimaryKey(id);
}

在PorscheServiceTest类中增加isExists测试方法

代码语言:javascript复制
@Test
public void isExists(){
    Integer id = 30;
    Boolean exists = porscheService.isExists(id);
    System.out.println("是否存在主键为"   id   "的记录:"   exists);
}

执行测试

查看控制台输出的SQL语句,通用Mapper根据count函数计算查询到的个数,如果为0就不存在返回False,如果为1就存在返回True。

0 人点赞