Java EE--框架篇(3-1)Mybatis

目录

前言

Hibernate/Mybatis

快速入门Mybatis(Springboot集成Mybatis)

单表增删改查

单表条件查询

***:#{param}和${param}的区别是?

***:查询结果是自定义类型,怎么办?

多表联合查询

分页查询


前言

带着问题学java系列博文之java基础篇。从问题出发,学习java知识。


Hibernate/Mybatis

前面无框架开发后台服务时(《Java EE--无框架开发后台服务》),数据库操作这块,没有借助任何框架,直接就是JDBC编程,整个需求实现下来可以说是痛苦万分。

1.每次操作数据库都要创建数据库连接,执行操作后又要释放连接,存在频繁的数据库连接创建、销毁;造成资源消耗,效率低下;

2.所有的操作都需要编写sql语句,并完成参数和sql语句的拼接,没有与实体类对象关联起来,操作复杂;

3.查询之后,需要遍历ResultSet结果集,完成结果集与对象实体的封装;

4.对于查询结果集、重复查询等没有任何缓存,每一次都需要从数据库查询数据;

5.不支持自动建表、更新表。

上面总结的痛点相信经历过纯手工JDBC编程的都深有体会,为了解决这些问题,所以推出了ORM(Object relationship mapping 对象关系映射)框架,旨在采用池技术统一管理数据库连接session、connection;自动封装对象实体类与数据表之间的关联(实体类与数据表关联,查询结果自动封装为具体对象);对查询结果可配置缓存(一级、二级缓存),对于重复查询可以从缓存中获取,提高效率;全自动的ORM框架还支持自动建表、更新表等。简单的说,有了ORM框架,将会发现数据库编程这块会彻底解放,甚至都不需要程序员去编写sql,一切都由框架准备好了。

下面就带领大家快速上手这两大框架,也经典的后台架构SSM/SSH中的第三个框架。(本博文都是基于SpringBoot(SpringBoot框架可以看成是Spring SpringMVC集合,后续博文会单独讲解),以全注解举例。如果想要详细学习框架,建议还是从配置阶段开始,逐渐过渡到全注解阶段。当然如果只是为了使用它,那直接学习全注解,能上手开发即可)

 

快速入门Mybatis(Springboot集成Mybatis)

建表语句:

CREATE TABLE `school`.`student` (
  `id` INT NOT NULL AUTO_INCREMENT COMMENT '学生编号',
  `name` VARCHAR(45) NOT NULL COMMENT '姓名',
  `age` INT(11) NOT NULL COMMENT '年龄',
  `address` VARCHAR(100) NULL COMMENT '居住地址',
  PRIMARY KEY (`id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8
COMMENT = '学生表';

CREATE TABLE `school`.`grades` (
  `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
  `student_id` INT NOT NULL COMMENT '外键关联学生表',
  `math` INT NOT NULL COMMENT '数学成绩',
  `chinese` INT NOT NULL COMMENT '语文成绩',
  `english` INT NOT NULL COMMENT '英语成绩',
  PRIMARY KEY (`id`),
  INDEX `student_id_idx` (`student_id` ASC) VISIBLE,
  CONSTRAINT `studentId`
    FOREIGN KEY (`student_id`)
    REFERENCES `school`.`student` (`id`)
    ON DELETE CASCADE
    ON UPDATE CASCADE)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8
COMMENT = '成绩表';

集成对接:

项目结构图:

1.实体类

@Data
@Table(name = "grades")
public class Grades implements Serializable {

    @Id
    @KeySql(useGeneratedKeys = true)
    private Integer id;

    @Column(name = "student_id")
    private Integer studentId;
    private Integer math;
    private Integer chinese;
    private Integer english;
}

@Data
@Table(name = "student")
public class Student {
    @Id
    @KeySql(useGeneratedKeys = true)  //mybatis通用mapper的注解,表明自增属性
    private Integer id;

    private String name;
    private Integer age;
    private String address;
}

2.mapper接口(依赖通用mapper实现)

public interface StudentMapper extends Mapper<Student> {
}

public interface GradesMapper extends Mapper<Grades> {
}

3.启动类添加注解扫描mapper

@SpringBootApplication
@MapperScan("com.zst.learnmybatis.mapper") //配置要扫描的mapper包名
public class LearnmybatisApplication {
    public static void main(String[] args) {
        SpringApplication.run(LearnmybatisApplication.class, args);
    }
}

4.配置信息

##日志中打印具体的sql语句
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
##指定扫描的实体类包名
mybatis.type-aliases-package=com.zst.learnmybatis.domain

##使用druid数据库连接池
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/school?useSSL=true&characterEncoding=utf-8&serverTimezone=GMT
spring.datasource.druid.username=root
spring.datasource.druid.password=***

5.所需依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.0.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.3</version>
        </dependency>
    </dependencies>

至此,springboot集成Mybatis(依赖通用mapper启动器)完毕,这里用了druid连接池,tk.mybatis.mapper。用通用mapper的目的,是为了简化编码,因为通用mapper基本实现了单表操作的所有方法,可以直接调用。

 

单表增删改查

@SpringBootTest
@RunWith(SpringRunner.class)
class StudentMapperTest {

    @Autowired
    StudentMapper studentMapper;

    //单表增删改查
    @Test
    public void testInsert(){
        Student student = new Student();
        student.setName("王五");
        student.setAge(18);
        student.setAddress("合肥");
        studentMapper.insert(student);
    }
}

单表增删改查等基本操作,可以直接使用通用mapper的基础方法,是不是非常简便。我们什么都没做,但是框架已经为我们实现所有!

下面梳理一下mapper中基本的增删改查方法:

添加方法:

mapper给我们默认实现了两个方法:insert()和insertSelective();它们分别的意思是:

insert():表示向表中插入一条数据,表列值就是该对象的属性值。如果有自增主键,且对象实例的主键属性也有值,则直接插入,如果对象主键属性没有值,则由数据库根据表数据自动生成。

insertSelective():区别insert(),这个方法仅给属性值不为null的列设定值,如果对象实例属性为null或者未设定值,则不做任何操作,表数据该列值由数据库默认填充。

 

删除方法:

mapper给我们实现了三个删除方法:deleteByPrimaryKey()、delete()、deleteByExample()

deleteByPrimaryKey():这个方法就如其名,根据主键删除;

delete():这个方法相当于条件删除,即删除表记录中列值符合对象实例属性值的所有记录(对象实例的属性值相当于and拼接的条件);

deleteByExample():这个方法完全就是条件删除,其中传入的example就是mybatis定义的专门用于条件拼接的类。

 

修改方法:

mapper给我们实现了四个修改方法:updateByPrimaryKey()、updateByExample()、updateByExampleSelective()、updateByPrimaryKeySelective()

updateByPrimaryKey():根据主键更新表记录,实体属性空值也会设置到数据表对应列;

updateByPrimaryKeySelective():根据主键更新表记录,实体属性空值也不会设置到数据表对应列,对应列的值由数据库已有值或者默认填充;

updateByExample():根据条件更新,相当于更新所有符合条件的表记录,实体属性值对应表的列,空值也会设置;

updateByExampleSelective():根据条件更新,相当于更新所有符合条件的表记录,实体属性值对应表的列,空值不会设置,由数据库已有值或者默认填充;

 

查询方法:

查询方法内置有很多个,分别解释一下意思:

select():将传入的实体类属性值作为条件,进行查询;

selectAll():查询表中所有记录;

selectByExample():根据传入的Example进行条件查询;

selectByRowBounds():将传入的实体类属性值作为条件,拼接上RowBounds的取结果限制(分页),进行查询;这个方法就是为了分页准备的,RowBounds支持两个参数offset和limit;

selectByExampleAndRowBounds():将传入的Example作为条件,拼接上RowBounds的取结果限制(分页),进行查询;(分页方法)

selectByPrimaryKey():根据主键查询;

selectCount():查询记录总数;

selectCountByExample():根据example条件查询,查询记录总数;

selectOne():将传入的实体类属性值作为条件,进行查询;必须确保符合条件的记录仅有一条,否则将会报错;

selectOneByExample():将传入的Example作为条件,进行查询,必须确保符合条件的记录仅有一条,否则将会报错;

 

单表条件查询

@SpringBootTest
@RunWith(SpringRunner.class)
public class StudentMapperTest {
    //单表条件查询
    @Test
    public void testExample(){
        Example example = new Example(Student.class);
        example.createCriteria().andLike("name","%二%")
                .orLike("address","%肥%");
        List<Student> students = studentMapper.selectByExample(example);
        for (Student student : students) {
            System.out.println(student);
        }
    }
}

如上,使用Example对象,拼接条件,然后进行条件查询。上例演示了查询姓名中含有“二”或者地址中含有“肥”的学生。

调用Example对象的createCriteria()方法,创建一个条件对象后,就可以调用相关方法,进行条件拼接。Example.Criteria对象具体方法如下:

addCriterion()、addOrCriterion() :添加自定义的条件(or 等同于 sql语句的or:或运算连接)

andIsNull()、andIsNotNull():值是空/非空(and 等同于 sql语句的and:与运算连接;类似方法,or连接符)

andEuqalTo()、andNotEqualTo():相等/不相等(类似方法,or连接符)

andLessThan()、andLessThanOrEqualTo():小于/小于等于(类似方法,or连接符)

andIn()、andNotIn():在取值范围内/不在范围内(类似方法,or连接符)

andBetween()、andNotBetween():在取值范围内/不在范围内(类似方法,or连接符)

andLike()、andNotLike():含有/不含有条件值(类似方法,or连接符)

 

也可以直接用Example的方法进行条件拼接:

@SpringBootTest
@RunWith(SpringRunner.class)
public class StudentMapperTest {
    //单表条件查询
    @Test
    public void testExample(){
        Example example = Example.builder(Student.class).andWhere(Sqls.custom().andLike("name","%二%")
                .orLike("address","%肥%")).orderByDesc("id").build();
        studentMapper.selectByExample(example);
    }
}

如上还加上了排序,演示了查询姓名中含有“二”或者地址中含有“肥”的学生,并按照id降序输出。

 

单表条件查询Example中的方法涵盖了所有情形吗?如果有些没有涵盖怎么办呢?

其实单表查询,使用Example基本可以满足所有场景了。如果真的遇到Example或者Example.Criteria中没有的,也可以通过手写sql来实现,范例如下:

public interface StudentMapper extends Mapper<Student> {

    @Select("select * from student where name like #{name} or address like #{address} order by id desc")
    List<Student> likeNameOrAddressOrderByIdDesc(String name,String address);
}

即只需要在Mapper接口中增加方法,编写sql语句就好。上例的方法和使用Example查询效果完全一致。

***:#{param}和${param}的区别是?

上例编写sql语句时,参数设定这块用了一个符号表达式#{name},它的意思是这里使用参数name。而mybatis还有另外一种符号表达式:${param}

如果上例使用这个表达式的话,具体如下:

select * from student where name like '%${name}%' or address like '%${address}%' order by id desc

可以看到${param}表达式,相当于字符串占位符,仅仅是将参数作为字符串填充进sql语句。

从这两个sql语句可以看出来,很明显使用#{param}更好一点,原因很简单:使用#{param}相当于使用了PrepareStatement,向其中添加参数,执行效率更高,而且没有sql注入漏洞;而使用${param}相当于使用了statement,只是一个字符串填充,执行效率低,且存在sql注入漏洞。因此推荐使用#{param}表达式,不过要注意,像本例中like查询的时候,需要对传参name手动加上“%%”,然后再作为方法传参。

***:查询结果是自定义类型,怎么办?

mapper中自带的方法返回类型都是实体类或者实体类集合等,如果我们要求返回自定义类型,该怎么办呢?显然此时就无法使用mapper自带的方法了,需要我们在mapper接口类中编写sql,自定义返回类型,如下例:

public interface StudentMapper extends Mapper<Student> {

    @Select("select name,age,address from student where id = #{id}")
    @ResultType(StudentDto.class) //可以使用注解表明返回类型,也可以省略不写
    StudentDto queryByPrimaryKey(Integer id);
}


/**
 * 自定义返回类型实体类
 * 注意属性名要和查询语句返回的列名一一对应,否则将无法转化
 * 本质:框架替我们封装了返回结果而已
 * ResultSet.getString("name")->name属性值
 * ResultSet.getString("address")->address属性值
 * ResultSet.getInt("age")->age属性值
 */
@Data
@ToString
public class StudentDto {
    private String name;
    private Integer age;
    private String address;
}


@SpringBootTest
@RunWith(SpringRunner.class)
public class StudentMapperTest {
    //自定义类型
    @Test
    public void testDefine(){
        StudentDto studentDto = studentMapper.queryByPrimaryKey(1);
        System.out.println(studentDto.toString());
    }
}

 

多表联合查询

通用mapper只有当前类对应的单表操作系列方法,如果要实现多表联合查询,则必须在mapper接口类中增加相应方法,编写sql语句。如下例:

@Data
@ToString
public class StudentScoreDto {
    private String name;
    private Integer math;
    private Integer chinese;
    private Integer english;
}

public interface StudentMapper extends Mapper<Student> {
    @Select("select s.name,g.math,g.chinese,g.english from student s left join grades g on s.id = g.student_id where s.id = #{id}")
    StudentScoreDto queryStudentScoreById(Integer studentId);
}

@SpringBootTest
@RunWith(SpringRunner.class)
public class StudentMapperTest {
    //多表查询
    @Test
    public void testMulti(){
        StudentScoreDto studentScoreDto = studentMapper.queryStudentScoreById(2);
        System.out.println(studentScoreDto.toString());
    }
}

本例演示了查询id为2的学生的姓名以及各科的分数,用到了自定义类型,以及编写sql语句(左连查询)。

 

分页查询

上面单表查询时分析过,mapper给我们写好了方法,支持分页查询,如下例:

@SpringBootTest
@RunWith(SpringRunner.class)
public class StudentMapperTest {    
    //分页查询
    @Test
    public void testPageQuery(){
        List<Student> students = studentMapper.selectByRowBounds(null, new RowBounds(3, 5));
        for (Student student : students) {
            System.out.println(student);
        }
    }
}

从上图可以看到其实还是查询了所有数据,只是在返回数据时,框架给我们做了数据筛除。其它的借助RowBounds对象实现分页查询方法也类似。另外使用这个方法有个缺陷,那就是需要我们每次计算出offset(偏移量),然后作为参数出入方法。显然,这种查询效率低,还需要我们每次都计算偏移量的分页方式很不友好。为此,业内出了各种帮助分页查询的小插件框架,比较著名的有PageHelper,下面以PageHelper举例:

@SpringBootTest
@RunWith(SpringRunner.class)
public class StudentMapperTest {
    //分页查询
    @Test
    public void testPageQueryByHelper(){
        //开启分页
        PageHelper.startPage(2,5);
        List<Student> students = studentMapper.selectAll();
        PageInfo<Student> pageInfo = new PageInfo<>(students);
        for (Student student : students) {
            System.out.println(student);
        }
        System.out.format("total:%s,pageSize:%s,pageNum:%s",pageInfo.getTotal(),pageInfo.getPageSize(),pageInfo.getPageNum());
        System.out.println();
        for (Student student : pageInfo.getList()) {
            System.out.println(student);
        }
    }
}

可以看到,使用PageHelper进行分页查询,虽然看起来还是调用mapper的selectAll()查询所有,但其实是由PageHelper接管了sql语句,分别执行了两次sql查询,首先查询出记录总数,然后PageHelper会根据传入的参数(PageNum 页码,PageSize 每页记录数)计算出偏移量,作为sql查询 limit 语句后的参数填入。进行第二次查询,获得分页数据。PageHelper虽然分为两次查询,但是两次都是小数据量返回,对于表数据非常多的情形,很明显比一次查询所有记录效率要高得多,且占用内存也更小。因此推荐使用PageHelper进行分页查询,不建议使用mapper自带的方法。


以上系个人理解,如果存在错误,欢迎大家指正。原创不易,转载请注明出处!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值