MyBatis使用教程详解<上>

一. 什么是MyBatis?

  • Mybatis是一个持久层框架,用于简化JDBC的操作
  • MyBatis原本是Apache的一个开源项目ibatis,后来更名为MyBatis

上面我们提到了一个概念----持久层

不知道小伙伴们有没有想到五大注解的关系,类似于下图

其中MyBatis就是Mapper层的框架,是基于JDBC的封装,可以帮助我们更方便的操作数据库.

二. MyBatis入门

MyBatis操作数据库的步骤:

  1. 创建SpringBoot项目,准备数据库,实体类...
  2. 引入MyBatis依赖
  3. 编写SQL语句(注解/XML)

2.1 创建MyBatis项目

创建工程不必多说,但是这次我们要导入MyBatis的依赖

可以在创建项目时直接添加,也可以创建项目后使用Spring Boot Helper插件引入依赖.

下面演示一下创建项目后的引入依赖.

点开pom.xml文件->右键单击,选择Generate,就可以看到下面这样一个小框

选择第一个->点击OK,就可以看到创建项目时的引入依赖界面

接下来需要在SQL中找到MyBatis,因为此处博主操作的是MySQL,所以也需要引入MySQL Driver依赖.

 然后就可以在pom.xml中看到引入的依赖了

这时候点击启动项目,会启动失败,提示信息显示没有指定数据源的URL,因此我们需要修改一下配置文件.

2.2 数据库准备&修改Spring配置

这里演示的是.yml配置文件,properties配置文件读者可自行操作.

在修改配置时,我们需要先创建一个数据库(博主创建的数据库名为config1114)

 然后在.yml文件中添加下列项(同学们不必记忆,只需要复制粘贴即可):

spring:datasource:url: jdbc:mysql://127.0.0.1:3306/config1114?characterEncoding=utf8&useSSL=falseusername: rootpassword: '******'driver-class-name: com.mysql.cj.jdbc.Drivermybatis:mapper-locations: classpath:mapper/*Mapper.xmlconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true

为了演示方便, 这里博主创建了两个表:User表和Student表

 

 接下来我们需要创建实体类,建立Java对象和数据库中表的映射

//Student表对应的实体类
@Data
public class Student {private Integer id;private String name;private Integer age;private Date createTime;private Date updateTime;
}
//User表对应的实体类
@Data
public class User {private Integer id;private String name;private Integer age;}

2.3 写持久层代码

持久层操作数据库有两种方式:

1. 使用注解

2. 使用xml文件

我们简单演示一下第一种,先创建一个持久层的接口UserMapper

@Mapper
public interface UserMapper {@Select("select * from user")List<User> selectAll();
}

@Mapper是ibatis(MyBatis的前身)提供的注解,表示MyBatis创建了这个对象,并将该对象交给Spring管理.

这里的Mapper必须是接口类型,因为它的方法不是由程序猿实现的,而是交给MyBatis实现

 2.4 测试持久层方法

我们知道,在启动SpringBoot项目后,需要前端进行访问才可以调用后端写的方法,这样做未免太过麻烦.我们可以直接使用单元测试类解决.

右键Generate->点击test

按照上图进行操作,这是我们会发现test目录下多了一个对应的UserMapperTest类

下面是这个类的代码.

@SpringBootTest//测试类进行方法测试时需要启动Spring容器
@Slf4j//使用该注解方便进行日志打印
class UserMapperTest {@BeforeEachvoid setUp() {log.info("所有测试方法执行前执行");}@AfterEachvoid tearDown() {log.info("所有测试方法执行后执行");}@Autowiredprivate UserMapper userMapper;//注入userMapper对象@Testvoid selectAll() {List<User> userList=userMapper.selectAll();log.info("查询全部User:{}",userList);}
}

下面我们点击selectAll方法旁边的倒三角进行测试(Test类的每个方法都可以独立运行).

可以看到打印日志上显示了全部的查询结果(博主自己提前加的数据).

 三. 使用注解实现方法

3.1 参数传递

刚才我们简单演示了使用注解查询user表中的全部数据,如果想要指定查询条件该怎么办呢?

这就需要借助#{}向SQL语句中传递参数.

@Select("select * from user where id=#{id}")//第一个id是user表中的字段名,第二个id是传递的参数名
User selectById(Integer id);

 现在在测试类中针对selectById()生成一个测试方法.

 @Testvoid selectById() {User user=userMapper.selectById(5);log.info("根据Id查询user:{}",user);}

可以看到查询结果(也是博主自己偷偷加的数据).

可能有些友友疑惑这个SQL语句是从哪里来的?这实际上是yml配置文件中设置的,用于开发环境中,帮助程序猿调试代码.

同时也可以看到 ,上面的SQL语句和我们使用JDBC时的语句是一样的,MyBatis框架是对JDBC进行了封装.

  •  如果SQL语句只需要传递一个参数,则这个参数可以任意命名(但不推荐).

这是再次使用运行测试方法,仍然能拿到刚才的数据

  •  可以使用@Param给传递的参数设置别名,这时SQL中的名字必须和注解内的名字对应.
@Select("select * from user where name=#{userName}")//name是user表中的字段名,userName是@Param中的参数
User selectByName(@Param("userName") String name);

针对这个方法生成一个测试方法.

@Test
void selectByName() {User user=userMapper.selectByName("李白");log.info("根据name查询user:{}",user);
}

下图为查询结果. 

3.2 @Insert

刚才我们展示的是@Select注解,现在来学习Insert操作.

@Insert("insert into user(name,age) values(#{name},#{age})")//name是传递的user对象的name属性,age是user对象的age属性int insertUser(User user);
  • insert,delete,updata这些更新数据库的操作都默认返回int表示表中更新的行数.(实际上是MySQL返回的)

针对这个方法生成一个测试方法.

@Test
void insertUser() {User user=new User();user.setName("薛宝钗");user.setAge(21);log.info("插入user结果:{}",userMapper.insertUser(user));}

 我们可以根据打印日志查看插入结果.

  •  如果传递的参数是一个对象,并且使用了@Param注解,那么#{}中的属性名就必须是对象名.属性的形式

这时候我们再次运行测试方法,会抛出异常

 需要对原方法进行更改.

 再次运行测试方法,即可运行成功


 上面的步骤中,我们只是向数据库插入了一条记录,但是不知道这条记录的主键id,下面演示一下如何获取自增主键,需要借助另一个注解@Options

@Insert("insert into user(name,age) values(#{name},#{age})")@Options(useGeneratedKeys = true,keyProperty = "id")//此处的id对应的是Java对象的属性名,表示将user表中的自增主键赋给哪个属性
int insertUserWithGetId(User user);

为该方法创建测试方法

@Test
void insertUserWithGetId() {User user=new User();user.setName("史湘云");user.setAge(19);userMapper.insertUserWithGetId(user);log.info("插入user:{}",user);
}

 可以从打印日志中看到插入该对象后的Id

3.3 @Delete

童靴们肯定都熟悉这个套路了,下面直接进行演示.

Mapper方法实现

@Delete("delete  from user where id=#{id}")
int deleteById(Integer id);

测试方法实现 

@Test
void deleteById() {int result=userMapper.deleteById(20);log.info("删除条目:"+result);
}

测试结果 

3.4 @Update

Mapper方法实现

@Update("update user set name=#{name},age=#{age} where id=#{id}")
int updateById(User user);

 测试方法实现

@Testvoid updateById() {User user=new User();user.setName("张爱莲");user.setAge(28);user.setId(21);int result=userMapper.updateById(user);log.info("更新条目: "+result);}

 测试结果

当然了,如果不放心可以直接查询数据库查看是否更新完成.

 

3.5 @Select

同学们肯定会疑惑,我们不是一开始演示的就是查询操作嘛.

刚才我们的查询操作中,Java对象的属性名和user表中的字段名是完全一致的,所以从user表中查询的字段值可以直接赋给对象的成员变量,但是如果二者不一致该怎么办呢? 

下面有三种解决办法:

  • 将查询的数据库字段重命名
  • 使用@Results注解
  • 修改yml配置文件(推荐)

这时我们借助的是Student表,对应的实体类属性见右图.

 

如果我们按照原来的方式进行查询 :

先创建一个StudentMapper接口.

@Mapper
public interface StudentMapper {@Select("select * from student")List<Student> selectAll();
}

在创建一个对应的测试类并在该类中注入StudentMapper接口.

@SpringBootTest
@Slf4j//使用Slf4j提供的对象进行日志打印
class StudentMapperTest {@Autowiredprivate StudentMapper studentMapper;//注入StudentMapper对象@Testvoid selectAll() {List<Student> studentList=studentMapper.selectAll();log.info("查询全部Student:{}",studentList);}
}

 点击方法旁边的倒三角,可得到下图中类似的查询结果(我插入的)

 可以看到,所有查询出来的对象,其createTime和updateTime成员的值均为null,这是因为没有与数据库中查询的字段对应.


先来演示第一种----对查询的数据库字段重命名

@Select("select id,name,age,create_time as createTime,update_time as updateTime from student")
List<Student> selectAll();

再来运行测试方法,可以看到没有createTime和updateTime属性都被赋上了值.

 同学们肯定会觉得,select后面跟上数据库中的字段名未免过于麻烦,不如直接使用*简单,但是实际开发过程中,我们最好还是使用前者,因为*所查询的数据量往往是非常大的,这就导致了效率变低.

下面演示第二种方法----使用@Results注解

@Select("select * from student")
@Results({@Result(column = "create_time",property = "createTime"),@Result(column = "update_time",property = "updateTime")})//column对应的是数据库中的字段名,property对应的是Java对象中的属性名
List<Student> selectAll2();

生成该方法的测试方法

@Testvoid selectAll2() {List<Student> studentList=studentMapper.selectAll2();log.info("查询全部Student:{}",studentList);}

 可以看到,createTime和updateTime成员变量同样拿到了对应的值

 如果别的方法也需要使用这种映射关系的话,只需给@Results注解添加一个id值,然后借助@ResultMap注解实现复用

@Select("select * from student where id=#{id}")
@ResultMap("map")
Student selectById(Integer id);

生成测试方法

 @Testvoid selectById() {Student student=studentMapper.selectById(1);log.info("根据id查询Student:{}",student);}

 

 接下来展示第三种,也是最简单的一种----借助yml配置文件解决

我们只需要给.yml配置文件添加一项信息

 重新编写一次查询方法

@Select("select * from student")
List<Student> selectAll3();
@Testvoid selectAll3() {List<Student> studentList=studentMapper.selectAll3();log.info("查询全部Student:{}",studentList);}

可以看到,这次即便我们什么也不做,同样获取到了响应的值

这里不得不强调一下企业的命名规范:

  1.  表名,字段名使用小写字母或数字,单词之间以下划线分割
  2. Java成员变量名,使用小驼峰的形式,第一个单词首字母小写,另外的单词首字母大写
  3. Java类名,使用大驼峰的形式,所有单词的首字母均大写

四. 使用XML文件实现方法

 学习了使用注解的方式操作数据库,与JDBC相比是不是灰常简单!但是这种方式只能完成一些简单的SQL语句,如果想要实现复杂的功能,需要借助XML文件.

4.1 配置文件&XML文件准备

如果想借助XML实现接口的方法,我们需要先告诉配置文件XML文件的位置.

我们现在resources目录下建一个mapper目录,然后建一个与UserXMLMMapper对应的xml文件.

 

然后在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">
<!--与这个xml文件对应的接口-->
<mapper namespace="com.example.demo.mapper.UserXMLMapper"></mapper>

细心的同学会发现我的idea界面上有两个魂斗罗图标,这实际上是MyBatisX插件.童靴们自行在Idea上安装即可.

MyBatisX是基于MyBatis的又一层封装.

 

 4.2 <insert>

刚才我们已经准备好了一个接口UserXMLMapper,现在尝试实现一个增加user的方法.

@Mapper
public interface UserXMLMapper {int insertUser(User user);
}

在接口中定义好方法后,需要在xml文件中添加一项标签<insert>.

如果我们的操作正确,会发现MyBatisX插件自动给我们添加了一对小鸟,点击它们中任意一个可以跳转到对方的位置.

 接下来在<insert>标签中编写SQL语句实现这个方法,与注解中的SQL语句一样,参数需要使用#{}传递.

    <insert id="insertUser">insert into user(name,age) values(#{name},#{age})</insert>

接着对这个方法进行单元测试.

@SpringBootTest
@Slf4j
class UserXMLMapperTest {@Autowiredprivate UserXMLMapper userXMLMapper;//注入UserXMLMapper对象@Testvoid insertUser() {User user=new User();user.setName("欧阳修");user.setAge(78);int result=userXMLMapper.insertUser(user);log.info("增加条目:{}",result);}
}

我们可以在日志打印上发现结果.


在讲解@Insert的时候,我们提到可以使用@Options获取自增主键,那么在xml文件中如何获取呢?

需要给<insert>标签添加额外的参数.

    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">insert into user(name,age) values(#{name},#{age})</insert>

 细心的童靴会发现这两个参数与@Options中的参数是一样滴.

这次我们插入对象后打印该对象.

    @Testvoid insertUser() {User user=new User();user.setName("欧阳修");user.setAge(78);int result=userXMLMapper.insertUser(user);log.info("增加条目:{}",result);log.info("插入对象:{}",user);}

这样就拿到了自增id

 4.3 <delete>

话不多说,我们直接cue流程.

UserXMLMapper方法定义.

int deleteById(Integer id);

 <delete>标签实现.

    <delete id="deleteById">delete from user where id=#{id}</delete>

测试方法编写.

    @Testvoid deleteById() {int result=userXMLMapper.deleteById(23);log.info("删除条目:"+result);}

4.4 <update>

 UserXMLMapper方法定义.

int updateById(User user);

<update>标签实现 

    <update id="updateById">update user set name=#{name},age=#{age} where id=#{id}</update>

单元测试方法实现.

@Testvoid updateById() {User user=new User();user.setId(13);user.setName("西施");user.setAge(24);int result=userXMLMapper.updateById(user);log.info("更新条目:"+result);}


上述的update方法,我们是根据user对象中的id找到了这个记录并进行了修改.当然,我们可以额外传递一个id指定某条记录进行更新.

int updateById2(User user,Integer id);
    <update id="updateById2">update user set name=#{name},age=#{age} where id=#{id}</update>

 编辑并运行下面的测试方法.

    @Testvoid updateById2() {User user=new User();user.setName("林黛玉");user.setAge(19);int result=userXMLMapper.updateById2(user,13);log.info("更新条目:"+result);}

这时候我们发现代码抛出了异常..仔细观察,是不是和使用@Param时没有指定对象名的异常类似?

这时候name和age字段名需要显式指定 对象名.属性名

    <update id="updateById2">update user set name=#{user.name},age=#{user.age} where id=#{id}</update>

再次运行测试方法,通过日志可发现修改成功.

童靴们会不会这样想,是不是因为传入的参数id与User对象中的id属性名字发生冲突了,所以MyBatis才会找不到这个参数.

于是乎,博主将传入的id改了个名字iid

 但是运行测试方法时,仍然抛出了同样的异常.

如果传入的是多个参数,需要以 对象名.属性名 的方式组织,否则MyBatis会找不到传递的参数.

4.5 <select>

前面方法的实现,标签中只需添加一个属性即可.但是<select>标签至少需要两个属性.

selectAll()方法定义.

List<User> selectAll();

<select>标签实现

<!--    id的值为对应的方法名,resultType指定返回的数据类型--><select id="selectAll" resultType="com.example.demo.entity.User">select * from user</select>

生成对应的测试方法.

    @Testvoid selectAll() {List<User> userList=userXMLMapper.selectAll();log.info("查询全部user:{}",userList);}


同样地,当数据库中的字段与对象的属性不一致时,有三种办法来实现.

  1. 对数据库中查询的字段重命名
  2. 使用<resultMap>标签
  3. 开启驼峰命名 

这次我们使用的依然是Student表.

第一种,第三种方法我们不再进行演示,与注解的使用是一样的.下文只演示第二种方法. 

先来创建一个StudentXMLMapper类,并定义selectAll方法.

@Mapper
public interface StudentXMLMapper {List<Student> selectAll();
}

再来配置xml文件,并生成对应的<select>标签.

<!--    id是这个resultMap标签的唯一标识,type是要映射的类--><resultMap id="map" type="com.example.demo.entity.Student">
<!--        id指定主键及主键对应的属性,result标签中的column指定数据库中的字段,property指定对象的属性名--><id column="id" property="id"></id><result column="create_time" property="createTime"></result><result column="update_time" property="updateTime"></result></resultMap>
<!--    不能继续使用resultType属性了,修改为resultMap属性并指定对应id--><select id="selectAll" resultMap="map">select * from student</select>

生成对应的测试方法并运行.

@SpringBootTest
@Slf4j
class StudentXMLMapperTest {@Autowiredprivate StudentXMLMapper mapper;@Testvoid selectAll() {List<Student> list=mapper.selectAll();log.info("查询全部Student:{}",list);}
}

 可以在打印结果中看到,createTime和updateTime正确地显示出来了.

五. 多表联合查询

上面的演示中,我们查询的都是单表,现在来演示一下如何查询多个表.

5.1 数据库准备

这次要用到的是user表和article表.

 

实际上,查询多个表和查询单个表是一样的操作,关键在于对应的实体类怎么定义.

我们需要在entity包下定义两个实体类对象.

@Data
public class User {private Integer id;private String name;private Integer age;}
@Data
public class Article {private Integer id;private String title;private String content;private Integer authorId;private Date createTime;private Date updateTime;
}

现在,我们的要求是-----查询全部Article并且显示与其对应的作者名.

于是我们需要定义一个视图类来建立与查询数据的映射.

@Data
public class ArticleView extends Article{private String name;
}

下面创建一个ArticleVIewMapper类,然后用注解的方式编写SQL语句

@Mapper
public interface ArticleViewMapper {@Select("select article.*,name from article,user where article.author_id=user.id")List<ArticleView> selectAll();
}

创建测试类并生成对应的测试方法.

@SpringBootTest
@Slf4j
class ArticleViewMapperTest {@Autowiredprivate ArticleViewMapper mapper;@Testvoid selectAll() {List<ArticleView> viewList=mapper.selectAll();log.info("查询结果:{}",viewList);}
}

现在来测试一下.

可以看到,打印结果并不是很理想,我们想要的是Article部分的全部信息,因为ArticleView类是Article类的子类,所以@Data帮助我们写的toString()方法并没有打印Article的信息. 

我们可以借助Idea重新生成一个toString()方法,这时代码在运行的时候会以我们写的方法为主.

@Data
public class ArticleView extends Article{private String name;@Overridepublic String toString() {return "ArticleView{" +"name='" + name + '\'' +"} " + super.toString();}
}

再次进行测试,就可以显示出全部的信息了.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/225734.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IDEA删除的文件怎么找回更新

一、 查找本地历史记录IDEA在进行代码版本管理时&#xff0c;会自动创建本地历史记录&#xff0c;如果我们误删了文件&#xff0c;可以通过查找本地历史记录来找回文件。 1.在项目中&#xff0c;选中被删文件的父级目录&#xff0c;“File”->“Local History”->“Show…

UE4 UE5 使用SVN控制

关键概念&#xff1a;虚幻引擎中使用SVN&#xff0c;帮助团队成员共享资源。 1. UE4/UE5项目文件 如果不需要编译的中间缓存&#xff0c;则删除&#xff1a; DerivedDataCache、Intermediate、Saved 三个文件夹 2.更新、上传

鸿蒙应用开发-初见:入门知识、应用模型

基础知识 Stage模型应用程序包结构 开发并打包完成后的App的程序包结构如图 开发者通过DevEco Studio把应用程序编译为一个或者多个.hap后缀的文件&#xff0c;即HAP一个应用中的.hap文件合在一起称为一个Bundle&#xff0c;bundleName是应用的唯一标识 需要特别说明的是&…

SpringCloud 微服务全栈体系(十八)

第十一章 分布式搜索引擎 elasticsearch 八、RestClient 查询文档 文档的查询同样适用 RestHighLevelClient 对象&#xff0c;基本步骤包括&#xff1a; 准备 Request 对象准备请求参数发起请求解析响应 1. 快速入门 以 match_all 查询为例 1.1 发起查询请求 代码解读&…

出海企业客服服务大作战:打造卓越客户体验的六大法宝!

全球化的加速推进&#xff0c;使得越来越多的企业开始涉足国际市场&#xff0c;进行跨境业务拓展。在这个过程中&#xff0c;优质的客服服务成为了出海企业获取竞争优势和留住客户的关键。本文将探讨出海企业如何做好客服服务&#xff0c;并重点阐述客服系统在提升服务质量和效…

FFA 2023|字节跳动 7 项议题入选

Flink Forward 是由 Apache 官方授权的 Apache Flink 社区官方技术大会&#xff0c;作为最受 Apache Flink 社区开发者期盼的年度峰会之一&#xff0c;FFA 2023 将持续集结行业最佳实践以及 Flink 最新技术动态&#xff0c;是中国 Flink 开发者和使用者不可错过的的技术盛宴。 …

【Linux】探索进程的父与子

目录 1.获取进程PID1.1进程PPID 2.通过系统调用创建进程-fork初识2.1为什么fork函数要给子进程返回0&#xff0c;给父进程返回pid&#xff1f;fork函数如何做到返回两次的&#xff1f;fork干了什么事情&#xff1f;怎么理解一个变量为什么有两个不同的值&#xff1f;如果父子进…

1742. 盒子中小球的最大数量

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能&#xff0c;轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/maximum-number-of-balls-in-a-b…

附录11-math.h的常见方法

stdlib.h是做数学计算的头文件 目录 1 数学知识 1.1 弧度值/π 角度值/180 1.2 双曲函数 2 math.h 2.1 反余弦值 acos() 2.2 反正弦值 asin() 2.3 反正切值 atan() 2.4 两个数的反正切值 atan2() 2.5 向上取整 ceil() 2.6 余弦值 cos() 2.7 双曲余弦 c…

玻色量子对外合作

2023年 2023.7 首个央企量子云计算项目&#xff0c;中标&#xff01; 2023.6 勇闯“量子电力”新领域&#xff0c;玻色量子与清大科越达成战略合作 2023.5 玻色量子签约移动云“五岳”量子云计算创新加速计划&#xff01; 2023.3 “量子计算通信”&#xff01;玻色量子与…

agv配置

要求前方避障停车距离1500m 货架旋转点要求遇障检测距离500m

my.ini添加了一句后又删除了,重启却失败的解决办法

背景&#xff1a;添加了一句&#xff0c;然后保存了&#xff0c;之后打开删掉了&#xff0c;结果就无法启动了&#xff0c;最后另存为ANSI格式&#xff0c;再把这个格式文件覆盖my.ini即可解决