在学习了数据库之后,要想开发一个web应用,设计好了数据库,那么就必须使用java来操控我们的数据库,那么我们使用mybatis框架来进行数据库的操作
那么什么是mybatis呢?
mybatis是针对数据库所开发的持久层框架,持久层也就是我们在三层架构思想中提出的dao层,用来访问数据,那么我们有了数据库,获取数据的方式就是通过mybatis,简化了JDBC的开发,JDBC是JavaEE中十三项规范的其中一个,主要用于规范访问数据。
那么了解了mybatis的用途,就来一起深入学习如何掌握mybatis
Mybatis入门
之前用图形化界面来编写sql语句,然后发送给数据库执行,数据库再将执行后的数据返回给图形化工具,mybatis操作数据库,就是在Java程序中来编写这条sql语句,然后发送个数据库执行,数据库再将结果返回给Java程序
一、mybatis快速入门
使用mybatis操作数据库的步骤
1、准备工作(创建springboot工程,数据库表user,实体类User)
最好是让数据库表中的字段名与java程序中的保持一致,这样就可以自动封装进Java实现类中
2、引入mybatis的相关依赖,配置mybatis
勾选mybatis的依赖,以及数据库的驱动,MySQL就勾选MySQL Driver即可
然后配置mybatis,点开resource中的application.properties输入以下内容
配置格式:
#配置数据库的连接信息
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接url
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=123456
3、编写SQL语句(注解/XML)
需要加入@Mapper注解,也就是Dao层,然后创建一个接口,使用@Select来进行查询操作,在括号后加入需要执行的sql语句,就会到数据库自动执行,然后将执行的结果封装到list方法中
4、进行单元测试
将Mapper层bean对象通过@Autowired依赖注入,然后通过@Test测试单元来进行,创建一个测试方法,因为输出整张表单,直接创建一个List集合,然后调用mapper接口中的list()方法查询所有的用户数据,最后使用stream流循环遍历整张表单
实操一下,先创建springboot项目,勾选相关依赖
mybatis的依赖以及mysql的驱动
建个MySQL表,名为user
然后来编写User类接收数据库查询到的信息,放在pojo包下
public class User {
private Integer id;
private String name;
public void setAge(Short age) {
this.age = age;
}
private short age;
public User() {
}
public User(Integer id, String name, short age) {
this.id = id;
this.name = name;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
我们知道了通过Java程序来将sql语句发送个数据库,那么我们得知道Java程序要将sql语句发送哪台数据库,所以我们需要对mybatis进行配置,与数据库建立连接信息
然后我们再创建一个接口UserMapper放在mapper包下,也就是dao层
import com.itxt.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper//运行时自动生成接口的实现类对象,然后将对象交给IOC容器
public interface UserMapper {
@Select("select * from user")
public List<User> list();
}
最后在springboot的测试单元下编写一个方法来遍历user表,然后得到需要的数据全部输出
import com.itxt.mapper.UserMapper;
import com.itxt.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest//springboot整合的单元测试
class SpringbootmybatisApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void testUser() {
List<User> userList = userMapper.list();
userList.stream().forEach(user -> {
System.out.println(user);
});
}
}
运行后发现user表中数据已经被一一输出,运行成功
二、配置sql提示信息
由于idea中会将其标为字符串,不知道sql语句是否编写正确,我们选中sql语句右键
显示上下文操作 >> 语言注入设置 >> MySQL
还有idea不识别表信息,是因为没有与数据库进行连接,在idea中配置与MySQL数据库连接即可,输入需要连接的数据库名mybatis
然后就会找到需要的数据库mybatis,那么建立连接成功就可以显示具体的表是否输入正确,以及在输入时可以具体看见是哪个数据库下的表单信息
三、JDBC
1、JDBC是由sun公司官方提供的一套操作所有关系型数据库的规范,即接口
2、各个数据库厂商去实现这个接口,提供数据库的驱动jar包
3、我们可以使用这套接口(JDBC)编程,真正执行代码的是jar包中的实现类
JDBC由各个厂商来实现,实现打包好成jar包,我们也称为驱动jar包
但随着技术的进步,逐渐由mybatis代替JDBC,JDBC中硬编码太多,如果在生产测试中不断的调换数据库,那是非常麻烦的。在获取数据时,大量的字段名就会非常繁琐,需要一个个来编码,而且在最后还要释放资源,大大降低了性能,资源也造成了浪费
而mybatis框架的优势就显现了出来,通过properties直接配置,动态连接数据库获取数据,并且在数据库连接池中,随用随取,用完了就放回去,不仅提高了代码性能,也减轻了资源的浪费
四、数据库连接池
那么数据库连接池是什么?又有何优势呢?
那么有了数据库连接池,在程序启动的时候,连接池就会初始化一定量的数据库连接对象,当有sql语句需要执行时,就从连接池中取出来需要的连接对象,执行完之后再归还这个连接对象,这样就做到了连接的复用,而不是每一次都创建一个连接,创建连接是比较耗费时间和资源的
但是由于连接池中的连接是共享的,那么当某个连接被客户端占用且没有归还的时候,连接池就会去检测这个客户端的连接空闲时间,如果空闲时间超过了预设的最大时间,就会将连接释放并且归还个连接池,来避免其他客户端没有连接可用,造成数据库的连接遗漏
每个连接池都需要去实现DataSource接口,以下四种都是配置好的现成数据库连接池
通常使用的连接池是Druid和Hikari,springboot项目中默认的连接池是Hikari
切换Druid连接池操作,有两种(引入依赖、属性配置)
引入依赖:就配置pom.xml文件将依赖引入即可
属性配置:直接在application.properties中将druid插入到datasource后面就可以
五、lombok工具
我们在编写实体类时,往往需要编写大量的属性,每一个都要去获取getter和setter方法,有需要时还要创建有参/无参构造方法,那么我们的代码就会非常的臃肿
如何解决这个问题呢?
我们在以后的编码中可以直接去使用lombok工具,我们只需要在实体类对象前加入一个@Data注解,就可以将上述的属性直接封装好,简化代码
lombok工具以及常用的注解
上面四种方法直接使用@Data注解即可,有/无参构造写入下面两种注解即可
@Getter //获取get方法
@Setter //获取set方法
@ToString //获取tostring方法
@EqualsAndHashCode //获取equals和hashcode方法
@Data //获取上面四种注解get、set、tostring、equals和hashcode方法
@NoArgsConstructor //获取无参构造
@AllArgsConstructor //获取有参构造
引入lombok依赖,在springboot的父工程中已经集成了lombok对依赖版本统一进行了管理,所以不用指定lombok的版本号,最后点击右侧Maven更新依赖就可以了
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
PS:lombok会在编译时,自动生成对应的java代码,我们使用lombok时需要安装插件(idea自带)除了老版本或其他IDE需要获取到这个插件
Mybatis基础操作
直接进行项目实例
那我们这里直接配置,创建好一个springboot工程
根据需求写入EmpMapper接口放在mapper包下,我们这里先写入实现类Emp,再创建一张MySQL表
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp {
private Integer id;//ID
private String username;//用户名
private String password;//密码
private String name;//姓名
private Integer gender;//性别 1 男 2 女
}
1、删除操作
我们进行删除的时候,是需要此记录的唯一标识,那么我们点击删除的时候,前端就会将删除操作的请求发送给服务端,那么请求的就是这条记录的唯一标识,所对应的也就是此记录的主键id了
PS: 如果我们的mapper中只有一个参数,那么#{id}中的参数可以随意写,因为只会传入这个参数
测试对id=5的用户进行删除操作,在mapper中写入删除的接口,然后在测试类中依赖注入,编写测试方法进行对删除操作的测试
@Mapper
public interface EmpMapper {
@Delete("delete from emp where id = #{id}")
public void delete(Integer id);
}
@SpringBootTest
class SpringbootmybatiscrudApplicationTests {
@Autowired
private EmpMapper empMapper;
@Test
public void testDelete() {
empMapper.delete(5);
}
}
测试直接通过,在MySQL表中记得点击刷新,更新表单信息,那么我们的删除操作就完成了
mybatis可以在控制台中来查看日志,看执行了哪些操作,如果需要查看的话我们进行配置就可以,在application.properies中配置
#mybatis日志查看
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
再执行控制台后就会发现这两行 ,这也称为预编译SQL操作
我们发现在mybatis中将id值后用?占位,而不是直接将我们输入的值赋值给id,那么这么做的优势是什么?
-------性能更高
因为MySQL在编译sql语句的时候会将sql语句进行四步,sql语法的解析检查、优化sql、编译sql、执行sql,那么前三步会当做一个缓存区,如果将id后的值写死等于某个值,在缓存中就会占用资源,不同的值sql每一次执行都会再编译一次,那么写成?占位符后,MySQL在处理时这条sql语句就只需要处理一次,后面只需要将变换不同的值来执行sql语句就可以了,大大提高了性能
-------防止SQL注入
SQL注入指通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。
如果不使用占位符,sql语句后面的赋值语句就会被直接拼接上去,如果将一个真值表达式拼接到sql语句后面,那么赋值表达式就为真,即使信息不匹配也可能会发生被攻击的现象
那么占位符就很好的解决了这个问题,只要我们在编写的时候将sql语句后的赋值使用占位符#{}就可以将整段字符串替换掉,不会存在以上的问题了,但是${}还是会存在拼接的问题,所以一般的使用时机就是在对表名和列表进行动态设置的时候使用就可以了
2、增加操作
先在EmpMapper接口中编写一个增加方法
@Insert("insert into emp(username, password, name, gender) VALUES (#{username},#{password},#{name},#{gender});")
public void insert(Emp emp);
这里的占位符中代表的是我们实体类的属性,然后再编写我们的测试方法,将实体对象一一赋值
@SpringBootTest
class SpringbootmybatiscrudApplicationTests {
@Autowired
private EmpMapper empMapper;
@Test
public void testInsert() {
Emp emp = new Emp();
emp.setUsername("la55");
emp.setPassword("123456");
emp.setName("la5");
emp.setGender(2);
empMapper.insert(emp);
}
}
最后测试我们的测试类,可以看到mybatis日志中显示了update操作,成功添加了数据
来看看我们的表变化,刷新一下可以看到id自增到6(前面执行过删除操作,但id还是会自增)
在有些时候我们会需要主键的值,比如套餐和菜品的关系,是多对多的关系,我们需要一张中间表(关系表)来维护两表的信息,那么就需要获取到主键值比如id
那么我们就需要新增操作
--------新增操作
新增操作:主键返回,在数据添加成功后,需要获取插入数据库数据的主键
那么如何实现?
我们只需要在Empmapper接口中再添加@Options注解即可,会将自动生成的主键值赋值给emp对象的id属性
@Options(useGeneratedKeys = true, keyProperty = "id")
//useGeneratedKeys = true代表我们需要获取主键值,keyProperty = “我们需要封装的emp对象的属性值”
我们再添加一个数据上去,打印emp.getId()方法返回的id值,运行后可以看到返回的主键值为7
刷新后可以看见我们新加入的用户,主键值就为7
3、更新操作
同样在EmpMapper中编写一个更新方法
@Update("update emp set username = #{username},name = #{name},gender = #{gender} where id = 1;")
public void update(Emp emp);
然后在测试类中再编写我们需要更改对象的属性,将username xt11改为xt666,name xt1改为xt
@SpringBootTest
class SpringbootmybatiscrudApplicationTests {
@Autowired
private EmpMapper empMapper;
@Test
public void testInsert() {
Emp emp = new Emp();
emp.setUsername("xt666");
emp.setName("xt");
emp.setGender(1);
empMapper.update(emp);
}
}
最后运行测试类,刷新一下,我们看到了更改的表单信息 ,那么更新操作就是这样了
4、查询操作
在EmpMapper接口中编写一个查询方法,根据id来查询员工返回一条记录,因为需要返回值,那么我们可以将数据封装到员工表Emp中进行返回
@Select("select * from emp where id = #{id}")
public Emp getById(Integer id);
编写测试类,测试我们查询操作,直接输出查询到此对象的信息
@SpringBootTest
class SpringbootmybatiscrudApplicationTests {
@Autowired
private EmpMapper empMapper;
@Test
public void testgetById() {
Emp emp = empMapper.getById(1);
System.out.println(emp);
}
}
查看日志查询到的数据与数据库中id值为1的数据相对应,根据id查询员工就完成了
那么对于条件查询,查询到的员工数据有很多条的时候,直接使用List集合,将泛型写成Emp实现类,将数据全部封装到员工表Emp中,如果其中的数据有范围,但封装的数据不能同时封装两个数据,我们直接创建集合里面写入形参定义这个集合就好了。
@Select("select * from emp where name like '%${name}%' and gender = #{gender}")
public List<Emp> list(Emp emp);
@SpringBootTest
class SpringbootmybatiscrudApplicationTests {
@Autowired
private EmpMapper empMapper;
@Test
public void testSelect() {
Emp emp = new Emp();
emp.setName("x");
emp.setGender(1);
List<Emp> emplist = empMapper.list(emp);
System.out.println(emplist);
}
}
可以看到根据这些条件查询到的用户记录有四条
PS:若对于条件查询的情况,进行模糊匹配的时候对于占位符#{}是不可以用"引号引起来的,所以如果是进行模糊匹配的条件查询,我们可以将#{}改换为${},直接将模糊匹配的结果进行拼接
但是这样的编写会存在sql注入,那我们如何避免这种情况呢?
那我们可以使用字符串拼接函数concat
这样避免了占位符被引号引住,还可以进行拼接字符串内容(推荐使用)
-------数据封装
针对某些字段名和实体类名不一致导致的封装失败,我们有三种解决方案
1、通过起别名(将不一致的字段名后起成实体类属性名)
2、通过@Results注解(直接将字段名手动映射成需要的实体类属性名)
3、通过配置mybatis驼峰命名(推荐)
代码段放在下边(前提是封装的实体类属性名都采用驼峰命名才可以进行映射)
#驼峰命名自动映射,a_java >> aJava
mybatis.configuration.map-underscore-to-camel-case=true
-------XML映射文件
注意创建xml文件的时候先创建目录,将这个xml文件放在与mapper接口同包下,注意目录创建不能使用 . 要使用 / 来创建,如下,然后在目录下创建一个xml文件
根据需求来选择需要的配置,这里进行映射sql语句,代码如下
<?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">
通过对EmpMapper的路径复制引用,赋值给namespace属性这样就完成了xml文件映射sql语句
配置了xml文件之后,将sql语句直接放入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="com.itxt.mapper.EmpMapper">
<!-- id 与mapper接口中的方法名一致,并且保持返回类型一致,若有返回值,则将返回类型赋值给resultType -->
<!-- resultType的返回值类型为sql语句单条记录所封装的类型,并且将需要该封装类的全限定名赋值给resultType -->
<select id="list" resultType="com.itxt.pojo.Emp">
select * from emp where name like concat('%',#{name},'%') and gender = #{gender}
</select>
</mapper>
使用mybatisx的话点击小蓝鸟可以跳转相应语句,编码更高效
alt+回车选择mapper接口方法可以自动生成SQL语句到xml文件中,会根据方法名中的关键字来识别相应的标签,比如xxxselectxxx方法就会自动生成<select>标签
选中SQL语句按住ctrl+alt+L格式化sql语句
-------总结注解操作和xml操作
使用注解来映射简单的sql语句会使代码看起来更简洁,但对于复杂一点的语句,Java注解不仅力不从心,还会让你的sql语句更加混乱不堪,因此SQL语句 简单用注解,复杂用xml
Mybatis动态SQL
随着用户的输入或者外部条件的变化,我们需要将写死的sql语句进行动态化
mybatis给我们提供了众多标签来支持xml文件中的动态sql,如<if><foreach><sql><include>等等
1、<if> 标签
条件结构判断的语法规则,注意的是在使用where时,需要写成<where>这样可以将<if>标签后多个连接的and或者or自动去除,在进行条件查询时动态化sql,判断条件是否成立,成立就拼接sql语句
扫描全表来动态获取需要的值
<select id="list" resultType="com.itxt.pojo.Emp">
select * from emp
<where>
<if test="name != null">
name like concat('%',#{name},'%')
</if>
<if test="gender != null">
and gender = #{gender}
</if>
</where>
</select>
进行动态更新操作,update中sql语句将各字段名使用逗号,隔开,那么每一行后面就会出现逗号,在我们更新时,若没有全部更新数据,只选择了其中几条,那么必然会有后面逗号的出现,sql语法就会报错,这时候我们使用一个<set>标签即可解决,与<where>相同会自动忽略
总结一下<if>、<where>、<set>标签
2、<foreach>标签
通常用于批量操作
在foreach标签下有五个属性collection(需要遍历的集合)item(遍历出来的元素)separator(分隔符)open(遍历开始前拼接的sql片段)close(遍历结束后拼接的sql片段)
我们先在mapper接口中定义一个批量删除方法deleteByIds
public void deleteByIds(List<Integer> ids);
然后配置xml文件,映射sql语句
<!-- 批量删除操作-->
<!-- collection(需要遍历的集合)-->
<!-- item(遍历出来的元素)-->
<!-- separator(分隔符)-->
<!-- open(遍历开始前拼接的sql片段)-->
<!-- close(遍历结束后拼接的sql片段)-->
<delete id="deleteByIds">
delete from emp where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
在test方法中测试批量删除,这里指定id为1,2,3的用户
@SpringBootTest
class SpringbootmybatiscrudApplicationTests {
@Autowired
private EmpMapper empMapper;
@Test
public void testDelete() {
List<Integer> ids = Arrays.asList(1,2,3);
empMapper.deleteByIds(ids);
}
}
查看控制台、数据库表emp已经成功删除id为1、2、3的用户,那么这就是<foreach>标签的作用,通常用于批量操作
3、<sql><include>标签
我们在xml文件中的sql语句过多,那么如果后期需要修改表名或者修改表中的字段名,就需要修改大量的SQL语句,那么我们这里使用<sql>标签将这些sql语句像java程序一样封装起来,我们使用的时候只需要插入<include>标签,将include的属性refid赋值为我们需要的sql语句的id名,就完成了xml中对sql语句的调用,简化项目
代码如下
先定义接口方法List
public List<Emp> list(String name,Integer gender);
配置xml文件,将重复sql片段放入<sql>标签中
<sql id="commonSelect">
select id, username, password, name, gender
from emp
</sql>
<select id="list" resultType="com.itxt.pojo.Emp">
<include refid="commonSelect"/>
<where>
<if test="name != null">
name like concat('%',#{name},'%')
</if>
<if test="gender != null">
and gender = #{gender}
</if>
</where>
</select>
对测试类方法进行编写,这里就不赋值,看看根据<sql><include>标签是否可以运行
// 根据条件查询
@Test
public void testList(){
List<Emp> empList = empMapper.list(null,null);
System.out.println(empList);
}
运行成功,就是按全表扫描
至此,我们简单高效的mybatis就学习完毕了