Spring-MyBatis基本操作
目录
一.什么是MyBtais
二.第一个MyBatis程序
1.构建数据库表
企业建表的规范:
2.创建项目引入依赖
3.编写代码
4.创建单元测试
三.使用MyBatis
1.通过注解使用Mybatis和Mysql数据库进行交互
a.@Insert(增)
b.@Delete(删)
c.@Update(改)
d.@Select(查)
对象中的成员属性和待查表中自动名称不匹配解决方法:
2.通过XML文件的方式使用MyBatis和MySQL数据库进行交互
使用前的配置:
a.增加
b.删除
c.修改
d.查找
三.多表查询
1.MySQL中的多表查询的SQL语句
2.MyBatis中的多表查询
四.#{} 和 ${}
1.相同之处
2.不同之处
使用#查询:
使用$查询:
$存在的漏洞:SQL注入
总结#和$的区别:
五.池化
一.什么是MyBtais
MyBatis是一款优秀的持久层(Dao层)框架,用于简化JDBC的开发。
MyBatis本是Apache的一个开源项目iBatis,2010年这个项目由apache迁移到goole code,并且改名为MyBatis。2013年11月迁移到GitHub。
官网:MyBatis中文网
持久层:指的是持久化操作的层,通常指的是操作数据的层(Dao层),用来操作数据库的。
二.第一个MyBatis程序
1.构建数据库表
-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
-- 使⽤数据数据
USE mybatis_test;-- 创建表[⽤⼾表]
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,`username` VARCHAR ( 127 ) NOT NULL,`password` VARCHAR ( 127 ) NOT NULL,`age` TINYINT ( 4 ) NOT NULL,`gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-⼥ 0-默认',`phone` VARCHAR ( 15 ) DEFAULT NULL,`delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now(),PRIMARY KEY ( `id` ) ) ENGINE = INNODB DEFAULT CHARSET = utf8mb4; -- 添加⽤⼾信息INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )VALUES ( 'admin', 'admin', 18, 1, '18612340001' );INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
企业建表的规范:
字段名/表名全部小写
表中必备的三个字段:id、gmt_create、gmt_modified
delete_flag是用来标记这个数据是否被删除了
逻辑删除:从逻辑的角度上删除了数据,但是数据依然保存在数据库中,只是在查找的时候不打印这个数据了。
物理删除:从硬盘的角度上将数据彻底的删除。(开发中很少用到这种删除)
2.创建项目引入依赖
这里下面的MariaDB Driver引入错误了,应该是MySQL Driver
配置数据库的相关信息:
配置文件中的代码https://blog.csdn.net/weixin_52159554/article/details/148771292?spm=1001.2014.3001.5502
试运行
3.编写代码
创建用于接收数据库中字段信息的对象
import lombok.Data;import java.util.Date;@Data
public class UserInfo {private Integer id;private String username;private String password;private Integer age;private Integer gender;private String phone;private Integer deleteFlag;private Date createTime;private Date updateTime;
}
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.example.springmabatisdemo.model.UserInfo;import java.util.List;@Mapper
public interface UserInfoMapper {@Select("select * from user_info")List<UserInfo> selectAll();
}
4.创建单元测试
import lombok.extern.slf4j.Slf4j;
import org.example.springmabatisdemo.model.UserInfo;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.List;import static org.junit.jupiter.api.Assertions.*;@SpringBootTest
@Slf4j
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userInfoMapper;@BeforeEachvoid setUp() {log.info("setUp");}@AfterEachvoid tearDown() {log.info("tearDown");}@Testvoid selectAll() {List<UserInfo> userInfos = userInfoMapper.selectAll();log.info("userInfos:"+ userInfos.toString());}
}
方便对比引入配置 :
配置文件中内容,在上面的链接中
打印日志是一个很消费性能的行为,因为在实际开发中,需要打印的日志非常的多,如果不进行筛选的情况下,会对系统性能造成一个很大的影响,所以在开发中只在关键的地方进行日志的打印,而且打印日志只在项目的开发阶段用于程序员开发使用。
开发环境:本地开发,一些公司也会有单独的服务器上员工进行开发测试,开发环境一般有专属于开发环境的数据库。
测试环境:给测试人员用于测试项目搭建的环境,包括测试数据库。
预发布环境:和发布环境一样的配置,但是不对外提供服务只是用于内部测试。在这个环境中和发布环境公用一个数据库服务器。
发布环境 :用户可以访问到项目的环境。
灰度发布:项目在完成测试和开发后不会一下子发布到全部的线上服务器中,而是只发布到少量的服务器中,也就是控制访问的流量,用于小面积的测试,判断项目是否有问题,然后一点一点的增大流量,直到发布到所有服务器中。
三.使用MyBatis
1.通过注解使用Mybatis和Mysql数据库进行交互
a.@Insert(增)
新增之前的数据表:
//1.指定新增的数据@Insert("insert into user_info (username,password,age,gender,phone)" +"values (\"张三\",\"123456\",\"18\",\"1\",\"15678881542\")")Integer insertOne();//2.通过接收对象来新增数据,并获取自增id@Options(useGeneratedKeys = true, keyProperty = "id")@Insert("insert into user_info (username,password,age,gender,phone)" +"values (#{username},#{password},#{age},#{gender},#{phone})")Integer insertTwo(UserInfo userInfo);//3.参数为对象时对参数进行重命名@Options(useGeneratedKeys = true, keyProperty = "id")@Insert("insert into user_info (username,password,age,gender,phone)" +"values (#{userInfo.username}," +"#{userInfo.password}," +"#{userInfo.age}," +"#{userInfo.gender}," +"#{userInfo.phone})")Integer insertThree(@Param("userInfo") UserInfo userInfo);
单元测试中的代码:
@Testvoid insertOne() {log.info(userInfoMapper.insertOne().toString());}@Testvoid insertTwo() {UserInfo userInfo = new UserInfo();userInfo.setUsername("李四");userInfo.setPassword("9874566");userInfo.setAge(25);userInfo.setGender(2);userInfo.setPhone("1345678912");log.info(userInfoMapper.insertTwo(userInfo).toString() + " " + "id:" + userInfo.getId());}@Testvoid insertThree() {UserInfo userInfo = new UserInfo();userInfo.setUsername("李四");userInfo.setPassword("9874566");userInfo.setAge(25);userInfo.setGender(2);userInfo.setPhone("1345678912");log.info(userInfoMapper.insertThree(userInfo).toString() + " " + "id:" + userInfo.getId());}
新增之后的数据表:
b.@Delete(删)
删除前的数据表:
//删除指定数据@Delete("delete from user_info where id=#{id}")Integer deleteOne(@Param("id") Integer id);
@Testvoid deleteOne() {log.info(userInfoMapper.deleteOne(1).toString());}
删除后的数据表:
c.@Update(改)
修改前的数据表:
//修改指定id的数据@Update("update user_info set age=#{age} where id=#{id}")Integer updateOne(UserInfo userInfo);
@Testvoid updateOne() {UserInfo userInfo = new UserInfo();userInfo.setId(2);userInfo.setAge(30);log.info(userInfoMapper.updateOne(userInfo).toString());}
修改后的数据表:
d.@Select(查)
@Select("select * from user_info")List<UserInfo> selectAll();
@Testvoid selectAll() {List<UserInfo> userInfos = userInfoMapper.selectAll();log.info("userInfos:"+ userInfos.toString());}
运行后发现有些成员为空:
原因是对象中的成员属性和待查表中自动名称不匹配:
对象中的成员属性和待查表中自动名称不匹配解决方法:
对mysql查询结果进行重命名:
这里一定要注意换行写的情况下,需要留意空格,不然会导致SQL语句错误
@Select("select id, username, password, age, gender, phone," +" delete_flag as deleteFlag, create_time as createTime, update_time as updateTime" +" from user_info")List<UserInfo> selectAll();
使用@Results注解
@Results(id="BaseMap", value={@Result(column = "delete_flag", property = "deleteFlag"),@Result(column = "create_time", property = "createTime"),@Result(column = "update_time", property = "updateTime")})@Select("select * from user_info")List<UserInfo> selectAllTwo();@ResultMap(value = "BaseMap")@Select("select * from user_info")List<UserInfo> selectAllThree();
@Testvoid selectAllTwo() {List<UserInfo> userInfos = userInfoMapper.selectAllTwo();log.info("userInfos:"+ userInfos.toString());}@Testvoid selectAllThree() {List<UserInfo> userInfos = userInfoMapper.selectAllThree();log.info("userInfos:"+ userInfos.toString());}
通过配置来实现自动转驼峰:
MyBatis自动转驼峰配置代码https://blog.csdn.net/weixin_52159554/article/details/148771292?spm=1001.2014.3001.5502 此时只需要将配置代码导入到properties或yml文件中,就可以不需要上面的操作,只使用最原始是@Select,就就可以让MyBatis自动完成映射。
2.通过XML文件的方式使用MyBatis和MySQL数据库进行交互
使用前的配置:
通过XML文件的方式和通过注释的方式第一步都是一样的,在properties或yml文件中配置连接mysql服务器的相关信息。
指明XML文件的路径
固定格式的XML文件代码https://blog.csdn.net/weixin_52159554/article/details/148797089?sharetype=blogdetail&sharerId=148797089&sharerefer=PC&sharesource=weixin_52159554&spm=1011.2480.3001.8118
import org.apache.ibatis.annotations.Mapper;
import org.example.springmabatisdemo.model.UserInfo;@Mapper
public interface UserInfoXMLMapper {}
a.增加
Integer insertOne(UserInfo userInfo);Integer insertTwo(@Param("userInfo")UserInfo userInfo);
<?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="org.example.springmabatisdemo.mapper.UserInfoXMLMapper"><insert id="insertOne" useGeneratedKeys="true" keyProperty="id">insert into user_info (username,password,age,gender,phone)values (#{username},#{password},#{age},#{gender},#{phone})</insert><insert id="insertTwo" useGeneratedKeys="true" keyProperty="id">insert into user_info (username,password,age,gender,phone)values (#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender},#{userInfo.phone})</insert>
</mapper>
import lombok.extern.slf4j.Slf4j;
import org.example.springmabatisdemo.model.UserInfo;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import static org.junit.jupiter.api.Assertions.*;@SpringBootTest
@Slf4j
class UserInfoXMLMapperTest {@Autowiredprivate UserInfoXMLMapper userInfoXMLMapper;@BeforeEachvoid setUp() {log.info("setUp");}@AfterEachvoid tearDown() {log.info("tearDown");}@Testvoid insertOne() {UserInfo userInfo = new UserInfo();userInfo.setUsername("李四");userInfo.setPassword("9874566");userInfo.setAge(25);userInfo.setGender(2);userInfo.setPhone("1345678912");log.info(userInfoXMLMapper.insertOne(userInfo).toString() + " " + "id:" + userInfo.getId());}@Testvoid insertTwo() {UserInfo userInfo = new UserInfo();userInfo.setUsername("李四");userInfo.setPassword("9874566");userInfo.setAge(25);userInfo.setGender(2);userInfo.setPhone("1345678912");log.info(userInfoXMLMapper.insertTwo(userInfo).toString() + " " + "id:" + userInfo.getId());}
}
b.删除
Integer deleteOne(@Param("id")Integer id);
<delete id = "deleteOne">delete from user_info where id = #{id}</delete>
@Testvoid deleteOne() {log.info(userInfoXMLMapper.deleteOne(5).toString());}
c.修改
Integer updateOne(@Param("id")Integer id,@Param("username")String username);
<update id = "updateOne">update user_info set username = #{username}where id = #{id}</update>
@Testvoid updateOne() {UserInfo userInfo = new UserInfo();userInfo.setId(2);userInfo.setUsername("wuhuwhuwhuw");log.info(userInfoXMLMapper.updateOne(userInfo.getId(),userInfo.getUsername()).toString());}
d.查找
使用XML文件进行查找,也会遇到字段名称和Java属性名称不匹配的情况,解决办法同注释的方法一样。
List<UserInfo> selectOne();List<UserInfo> selectTwo();
<resultMap id="XmlBaseMap" type="org.example.springmabatisdemo.model.UserInfo"><id column="id" property="id"></id><result column="delete_flag" property="deleteFlag"></result><result column="create_time" property="createTime"></result><result column="update_time" property="updateTime"></result></resultMap><select id = "selectOne" resultType="org.example.springmabatisdemo.model.UserInfo">select * from user_info</select><select id = "selectTwo" resultMap="XmlBaseMap">select * from user_info</select>
三.多表查询
在项目开发中最好尽量避免使用多表查询,尤其是在对性能有很高要求的项目中。因为多表查询是MySQL服务器帮我们进行的优化,我们无法选择是何种优化,这样在进行大量数据查询的时候会非常占用性能,所以使用多表查询必然很慢。
a.select * from ta
b.select * from tb
c.select * from ta left join tb ta.xx=tb.xx
对于上面的SQL代码,假设a,b代码的运行时间分别是10ms,则c代码的运行时间肯定会大于10ms,此时虽然a,b代码分别执行后的总时间是20ms,看似使用多表查询是更省时间的选择,但是对于a,b代码的运行,在实际的项目开发中我们是会对这样的操作进行优化的,比如引入多线程,此时就会比多表查询更加节省时间,但是多表查询是MySQL服务器帮我们进行的优化,我们无法直接选择。
而且通常情况下,数据库集群是很多项目一起使用的,当出现慢查询时,会影响整个数据库集群,从而也就影响了所有使用该集群的项目。
同时比如在执行很多类似于a,b代码的操作时,虽然会导致Java服务器的吞吐量变大,造成系统卡顿,但是此时我们只需要很简单的将Java服务器扩容就好了,这种服务器扩容的方式是非常简单的,但是如果你想要扩容数据库集群的话就不是很方便了,此时就需要让DBA(数据库管理员,是另一个团队)来做扩容,自己本来可以通过别的方法解决的事情,此时引入第三方,就不会那么方便了。
但是虽然多表查询在性能上的开销很大,但是多表查询会自动帮我们拼接我们需要查的多个表,帮我们对数据进行一些操作。
1.MySQL中的多表查询的SQL语句
添加新的数据表:
CREATE TABLE article_info (id INT PRIMARY KEY auto_increment,title VARCHAR ( 100 ) NOT NULL,content TEXT NOT NULL,uid INT NOT NULL,delete_flag TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',create_time DATETIME DEFAULT now(),update_time DATETIME DEFAULT now() ) DEFAULT charset 'utf8mb4';-- 插⼊测试数据INSERT INTO article_info ( title, content, uid ) VALUES ( 'Java', 'Java正⽂', 1
);
select ta.*,tb.username,tb.age from article_info taLEFT JOIN user_info tb on ta.uid = tb.idwhere ta.id=1
2.MyBatis中的多表查询
在项目中,我们获取多表查询数据的时候,并不在乎是多少个表组成的数据,所以只需要将需要数据构建成一个对象,将数据保存到这个对象中就可以了。
构建用于接收数据的对象:
import lombok.Data;import java.util.Date;@Data
public class ArticleInfo {private Integer id;private String title;private String content;private Integer uid;private Integer deleteFlag;private Date createTime;private Date updateTime;//接收用户信息private String userName;private Integer age;
}
使用使用注释的方法使用MyBatis操作数据库:
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.example.springmabatisdemo.model.ArticleInfo;@Mapper
public interface ArticleInfoMapper {@Select("select ta.*,tb.username,tb.age from article_info ta" +" LEFT JOIN user_info tb on ta.uid = tb.id\n" +" where ta.id= #{id}")ArticleInfo getArticleInfoById(Integer id);
}
单元测试代码:
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import static org.junit.jupiter.api.Assertions.*;@SpringBootTest
@Slf4j
class ArticleInfoMapperTest {@Autowiredprivate ArticleInfoMapper articleInfoMapper;@BeforeEachvoid setUp() {log.info("setUp");}@AfterEachvoid tearDown() {log.info("tearDown");}@Testvoid getArticleInfoById() {log.info(articleInfoMapper.getArticleInfoById(1).toString());}
}
四.#{} 和 ${}
1.相同之处
#{}和${}都是用来获取传入变量的值的。、
2.不同之处
使用#查询:
会自动根据传入的类型进行匹配:
会自动根据传入的数据的类型进行匹配的原因是,#是预编译SQL:
涉及排序操作的时候,不能使用# ,因为如果使用#会自动为其添加' ':
使用$就可以了:
进行模糊查询的时候也不可以使用#:
使用$就可以了:
使用$查询:
使用$并不会自动转换,给什么它就传什么,但是以username为条件的时候,需要' '引起来的字符串,#会自动添加,$不会。
解决办法:不论传输何种数据,都手动加上' ',这样MySQL服务器会自动帮我们进行优化,但是会产生一定的性能问题。
以下方法选其一:
$存在的漏洞:SQL注入
此时因为or也是SQL关键词,MySQL也就同时将or 1='1' 给执行了,'1'MySQL会自动优化为1,但是这并不是想要的结果。
因为$是即时的SQL,所以给它啥它就会立马赋值为啥,此时就会有问题,这是一个非常严重的问题,如果想上面的代码这样直接写,如果有人这样访问数据库,带上了DROP操作,此时损失就非常的大了。
此时使用#就可以了。
总结#和$的区别:
#是预编译SQL而$是即时SQL
预编译SQL性能高
预编译SQL不存在SQL注入问题
排序时不能用#,即时使用$时也要在代码中进行参数检查,防止代码注入的问题
表名,字段名等作为参数时,也不能使用#
模糊查询时,如果非要使用#,需要搭配mysql的内置函数concat
一般在项目开发中,能用#的先用#,实在不行了再用$。
五.池化
Hikari : SpringBoot默认使⽤的数据库连接池
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-3-starter</artifactId><version>1.2.21</version>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.17</version>
</dependency>