QA 由浅入深持久层框架(十一)- MyBatis 通用 Mapper(中)

「这是我参与2022首次更文挑战的第36天,活动详情查看:2022首次更文挑战

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

2.5 insert 相关方法

// 保存一个实体,null的属性也会保存,不会使用数据库默认值
int insert(T record);
// 保存一个实体,null的属性不会保存,会使用数据库默认值
int insertSelective(T record);
复制代码

2.5.1 insert 方法

在PorscheService中增加save方法

void save(Porsche porsche);
复制代码

在PorscheServiceImpl中实现save方法的实现

@Override
public void save(Porsche porsche) {
    porscheMapper.insert(porsche);
}
复制代码

在PorscheServiceTest中对save方法进行测试

@Test
public void save(){
    // 构造插入的实体类
    Porsche porsche = new Porsche();
    porsche.setPorName("Panamera 4S 2022");
    porsche.setPorPrice(880000d);
    porsche.setPorStock(10);

    porscheService.save(porsche);
}
复制代码

执行save测试方法 image.png 数据库更新了一行,插入成功。增加测试代码,输出自增的主键,再次执行测试

image.png 输出主键为null,实际插入数据库后已经生成了自增主键,只是程序没有获取到插入成功后生成的主键。 要获取生成的主键Value需要在Porsche实体类与数据主主键对应的属性上增加@GeneratedValue(strategy = GenerationType.IDENTITY) 再次执行测试

image.png 执行了两条SQL语句,一条插入语句,一条是查询最新的主键的Value,控制台成功输出插入成功后生成的自增主键的Value。

@GeneratedValue注解可以让通用Mapper在执行insert操作之后将数据库自动生成的自增主键的值回写到实体类中

2.5.2 insertSelective 方法

在PorscheService中增加savePorscheSelective方法

void savePorscheSelective(Porsche porsche);
复制代码

在PorscheServiceImpl中增加savePorscheSelective方法的实现

@Override
public void savePorscheSelective(Porsche porsche) {
    porscheMapper.insertSelective(porsche);
}
复制代码

增加savePorscheSelective测试方法

@Test
public void savePorscheSelective(){
    Porsche porsche = new Porsche();
    porsche.setPorName("Porsche 911 2022");

    porscheService.savePorscheSelective(porsche);
}
复制代码

非主键字段如果为null,就不会被加入到SQL语句中

image.png

适合实体类属性较多,但是执行插入时插入属性部位NULL的数据比较少的情况

2.6 update 相关方法

// 根据主键更新实体全部字段,null值会被更新
int updateByPrimaryKey(T record);
// 根据主键更新属性不为null的值
int updateByPrimaryKeySelective(T record);
复制代码

2.6.1 updateByPrimaryKeySelective

在PorscheService中增加updatePorscheSelective方法

void updatePorscheSelective(Porsche porsche);
复制代码

在PorscheServiceImpl中增加updatePorscheSelective方法的实现

@Override
public void updatePorscheSelective(Porsche porsche) {
    porscheMapper.updateByPrimaryKeySelective(porsche);
}
复制代码

增加updatePorscheSelective测试方法

@Test
public void updatePorscheSelective(){
    Porsche porsche = new Porsche();
    porsche.setPorId(1);
    porsche.setPorPrice(900000d);

    porscheService.updatePorscheSelective(porsche);
}
复制代码

执行测试

image.png

针对部分字段更新的实体类使用该方法 针对有值的字段进行更新,没有值的字段保持不变

2.7 delete 相关方法

// 根据实体属性作为条件进行删除,查询条件使用等号
int delete(T record);
// 根据主键字段进行删除,方法参数必须包含完整的主键属性
int deleteByPrimaryKey(Object key);
复制代码

2.7.1 delete

在PorscheService中增加removePorsche方法

void removePorsche(Porsche porsche);
复制代码

在PorscheServiceImpl中增加removePorsche方法的实现

@Override
public void removePorsche(Porsche porsche) {
    porscheMapper.delete(porsche);
}
复制代码

增加removePorsche的测试方法

@Test
public void removePorsche(){
    // 构造要删除的实体类
    Porsche porsche = new Porsche();
    porsche.setPorName("Panamera 4S 2022");
    porsche.setPorPrice(880000d);
    porsche.setPorStock(10);

    porscheService.removePorsche(porsche);
}
复制代码

执行测试

image.png

如果传入的对象为null,注释掉对象的setter方法后再次执行测试

image.png

数据被全部删除,使用这个方法一定要慎重,要进行条件判断

2.7.2 deleteByPrimaryKey

在PorscheService中增加removePorscheById方法

void removePorscheById(Integer id);
复制代码

在PorscheServiceImpl中增加removePorscheById方法的实现

@Override
public void removePorscheById(Integer id) {
    porscheMapper.deleteByPrimaryKey(id);
}
复制代码

增加removePorscheById测试方法

@Test
public void removePorscheById(){
    // 删除记录的主键
    Integer id = 1;
    porscheService.removePorscheById(id);
}
复制代码

执行测试

image.png

deleteByPrimaryKey最常用的删除的方法

三、通用Mapper实现复杂查询-ExampleMapper

3.1 QBC查询

QBC既Query By Criteria,QBC查询是将查询条件通过Java对象进行模块化封装。

Criterion是一个内部类,是Criterion的复数形式,既准则标准的意思;与MBG生成的复杂查询条件的实体类CatExample中的Criterion内部类代码是一样的,也都提供了createCriteria方法,创建复合查询条件

MBG生成的封装查询条件的XXXExample VS 通用Mapper的Example类 image.png 都封装了大量的查询条件。两者使用方法的不同,MBG直接针对Entity实体类生成了XXXExample,如在生成的CatExample、TeacherExample;而通用Mapper使用条件查询时则需要在新建一个Example时传入一个要查询的实体类class

Example porscheExample = new Example(Porsche.class);
复制代码

3.1.1 使用QBC查询

使用QBC创建如下复合查询

WHERE (por_id > 1 and por_stock > 20) OR ( por_price < 1000000 and por_stock > 20)
复制代码

在PorscheService中增加getPorschesByPriceAndStock方法

List<Porsche> getPorschesByPriceAndStock(Example porscheExample);
复制代码

在PorscheServiceImpl中增加getPorschesByPriceAndStock方法的实现

@Override
public List<Porsche> getPorschesByPriceAndStock(Example porscheExample) {

    return porscheMapper.selectByExample(porscheExample);
}
复制代码

在PorscheServiceTest增加getPorscheByPriceAndStock的测试方法,查询条件的构建过程查看注释。

@Test
public void getPorscheByPriceAndStock(){
    Example porscheExample = new Example(Porsche.class);
    // WHERE (por_id > 1 and por_stock > 20) OR ( por_price < 1000000 and por_stock > 20)
    // 创建两个查询条件,WHERE关键字后面的两个括号分别对应两个查询条件
    Example.Criteria criteria1 = porscheExample.createCriteria();
    Example.Criteria criteria2 = porscheExample.createCriteria();
    // 设置两个查询条件
    criteria1.andGreaterThan("porId",1)
            .andGreaterThan("porStock",20);

    criteria2.andLessThan("porPrice",1000000d)
            .andGreaterThan("porStock",20);

    // 通过OR连接查询条件1--criteria1和查询条件2--criteria2
    // porschExample就相当于select 字段名 from 表明 WHERE criteria 1
    // 只需要调用or方法将criteria2连接上,就构成了完成的SQL语句
    // select 字段名 from 表明 WHERE (criteria1) OR (criteria2)
    porscheExample.or(criteria2);

    List<Porsche> porscheList = porscheService.getPorschesByPriceAndStock(porscheExample);
    for (Porsche porsche : porscheList) {
        System.out.println("查询到的内容为:" + porsche);
    }
}
复制代码

执行测试

image.png

3.1.2 QBC其他设置

QBC查询同时可以设置如排序、去重、设置查询的字段等,在getPorscheByPriceAndStock测试方法中增加相关代码,

porscheExample.setDistinct(true);
porscheExample.orderBy("porPrice").asc().orderBy("porName").desc();
porscheExample.selectProperties("porName","porPrice");
复制代码

再次执行测试 image.png 查看控制台删除的SQL语句,构建的复合条件在SQL执行时生效。

四、通用Mapper实现分页-RowBoundsMapper

4.1 分页相关方法

// 根据example条件和RowBounds进行分页查询
List<T> selectByExampleAndRowBounds(Object example, RowBounds rowBounds);

// 根据实体属性和RowBounds进行分页查询
List<T> selectByRowBounds(T record, RowBounds rowBounds);
复制代码

4.1.1 selectByRowBounds

在PorscheService接口中增加分页查询方法getPorschesByRowBounds

List<Porsche> getPorschesByRowBounds(Porsche record, RowBounds rowBounds);
复制代码

在PorscheServiceImpl中实现分页查询方法getPorschesByRowBounds

@Override
public List<Porsche> getPorschesByRowBounds(Porsche record, RowBounds rowBounds) {

    return porscheMapper.selectByRowBounds(record,rowBounds);
}
复制代码

增加该方法的测试代码

@Test
public void getPorschesByRowBounds(){
    int pageNo = 2;
    int pageSize = 3;

    int index = (pageNo - 1) * pageSize;

    RowBounds rowBounds = new RowBounds(index, pageSize);

    List<Porsche> porscheList = porscheService.getPorschesByRowBounds(null, rowBounds);

    for (Porsche porsche : porscheList) {
        System.out.println("查询到的内容为:" + porsche);
    }
}
复制代码

执行测试 image.png

根据控制台输出的SQL语句,说明通用Mapper还是查询出所有的数据,在内存中进行的分页操作,”假分页“,不推荐使用这个分页查询,推荐使用PageHelper插件实现真正的分页

4.2 @Transient

一般情况下,实体类中的属性和数据库表中的字段都是一一对应的,但是有些属性可能在数据库中没有对应的字段,这时候就需要使用@Transient注解标明这不是表中的字段。

五、通用Mapper的逆向工程

5.1 通用Mapper的逆向工程

通用Mapper的逆向工程生成的代码与原生MyBatis生成的代码稍有区别,首先都包含Entity实体类、XxxMapper接口以及XxxMapper.xml,但是通用Mapper生成的Entity实体类中会包含如@Table注解、@Column注解、@Id注解以及@GeneratedValue等注解;生成的XxxMapper接口继承Mapper<Xxx>类;生成的XxxMapper.xml中没有SQL语句,只有一个resultMap标签。

5.2 使用通用Mapper的逆向工程

新建一个项目common-mapper-mbg,该项目的用到的依赖、Spring配置及MyBatis配置与general-mapper项目用到的一致,差异在于新建的general-mapper-mbg项目中不包含Entity实体类Mapper接口以及Service类和测试类等。

在common-mapper-mbg项目的pom.xml文件中增加通用Mapper的代码生成器依赖及maven插件,通过maven插件执行代码生成器生成代码,通过用Java代码或者命令行的方式也可运行代码生成器,这里不再赘述。

<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-generator</artifactId>
    <version>1.0.0</version>
</dependency>
复制代码

resources目录下新增generatorConfig.xml

<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <properties resource="db.properties"/>

    <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        <!--通用Mapper的MBG插件-->
        <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
            <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
            <property name="caseSensitive" value="true"/>
        </plugin>

        <jdbcConnection driverClass="${jdbc_driver}"
                        connectionURL="${jdbc_url}"
                        userId="${jdbc_username}"
                        password="${jdbc_password}">
        </jdbcConnection>

        <javaModelGenerator targetPackage="com.citi.entity"
                            targetProject="./src/main/java"/>

        <sqlMapGenerator targetPackage="mappers"
                         targetProject="./src/main/resources"/>

        <javaClientGenerator targetPackage="com.citi.mapper"
                             targetProject="./src/main/java"
                             type="XMLMAPPER"/>
        <!--tableName="%"表示所有表都参与逆向工程-->
        <table tableName="t_teacher" domainObjectName="Teacher">
            <generatedKey column="id" sqlStatement="Mysql"/>
        </table>

    </context>
</generatorConfiguration>
复制代码

pom.xml中增加插件

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.7</version>
            <configuration>
                <configurationFile>
                    ${basedir}/src/main/resources/generatorConfig.xml
                </configurationFile>
                <overwrite>true</overwrite>
                <verbose>true</verbose>
            </configuration>
            <dependencies>
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>8.0.16</version>
                </dependency>
                <dependency>
                    <groupId>tk.mybatis</groupId>
                    <artifactId>mapper</artifactId>
                    <version>4.0.0</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>
复制代码

运行通用Mapper的MBG

image.png 代码生成器运行成功

5.3 测试通用Mapper生成的代码

新建代码生成器生成的TeacherMapper的测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application.xml")
public class TeacherMapperTest {

    @Resource
    private TeacherMapper teacherMapper;

    @Test
    public void selectAll(){
        List<Teacher> teachers = teacherMapper.selectAll();
        for (Teacher teacher : teachers) {
            System.out.println("查询到的内容为:" + teacher.getTeacherName());
        }
    }

}
复制代码

执行该测试 image.png 控制台成功输出查询内容,通用Mapper的逆向工程生成的代码验证成功。

Guess you like

Origin juejin.im/post/7068304952325570596