目录
一.什么是MyBatis
入门程序初体验
二.MyBatis基本操作CRUD
▐ 增(Insert)
返回主键
▐ 删(Delete)
▐ 改(Update)
▐ 查(Select)
起别名
结果映射
开启驼峰命名(推荐)
三.MyBatis XML配置文件
▐ 增(Insert)
▐ 删(Delete)
▐ 改(Update)
▐ 查(Select)
#{} 和 ${}
一.什么是MyBatis
MyBatis是⼀款优秀的 持久层 框架,⽤于简化JDBC的开发。持久层:指的就是持久化操作的层, 通常指数据访问层(dao),是⽤来操作数据库的。
在使用前需要导入相关依赖,首先是MyBatis依赖
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency>
还有MySQL的依赖
<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency>
yml中相关配置
spring:datasource:url: jdbc:mysql://localhost:3306/数据库表名?useSSL=false&serverTimezone=UTCusername: 数据库用户名password: 数据库密码driver-class-name: com.mysql.cj.jdbc.Driver
如果使⽤ MySQL 是 5.x 之前的版本,驱动类使⽤的是"com.mysql.jdbc.Driver",如果是⼤于 5.x 使⽤的是“com.mysql.cj.jdbc.Driver”
入门程序初体验
准备好数据库和对应的实体类:
-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
-- 使⽤数据数据
USE mybatis_test;
-- 创建表[⽤⼾表]
DROP TABLE IF EXISTS userinfo;
CREATE TABLE `userinfo`
(`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(),PRIMARY KEY (`id`)
) ENGINE = INNODBDEFAULT CHARSET = utf8mb4;
-- 添加⽤⼾信息
INSERT INTO mybatis_test.userinfo (username, `password`, age, gender, phone)
VALUES ('admin', 'admin', 18, 1, '18612340001');
INSERT INTO mybatis_test.userinfo (username, `password`, age, gender, phone)
VALUES ('zhangsan', 'zhangsan', 18, 1, '18612340002');
INSERT INTO mybatis_test.userinfo (username, `password`, age, gender, phone)
VALUES ('lisi', 'lisi', 18, 1, '18612340003');
INSERT INTO mybatis_test.userinfo (username, `password`, age, gender, phone)
VALUES ('wangwu', 'wangwu', 18, 1, '18612340004');
实体类
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;
}
下面通过一段程序可以快速体验什么是MyBatis,相比传统使用JDBC的方式链接操作数据库,使用MyBatis只需要用2行代码就解决了问题
@Mapper
public interface UserInfoMapper {//查询所有的用户@Select("select username, `password`, age, gender, phone from userinfo")public List<UserInfo> queryAllUser();
}
@Mapper注解:表⽰是MyBatis中的Mapper接⼝
@Select注解:代表的就是select查询,也就是注解对应⽅法的具体实现内容
当 queryAllUser() 方法被调用的时候,就会自动执行@Select注解中的SQL语句
@Slf4j
@SpringBootTest
class MyBatisDemoApplicationTests {@Autowiredprivate UserInfoMapper userInfoMapper;@Testvoid contextLoads() {List<UserInfo> userInfos = userInfoMapper.queryAllUser();log.info("用户信息如下:" + userInfos);}
}
试运行则可以看见查询结果
但很显然,这样是不方便观察的,为此我们可以继续在yml中添加配置项,让日志记录更加方便我们阅读(在yml文件中配置项一定要保持缩进的一致,不然可能会报错)
mybatis:configuration: # 配置打印 MyBatis⽇志log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
再次运行,就可以更直观的看到SQL运行结果了
二.MyBatis基本操作CRUD
对于数据库的操作基本上概括来说就是增删改查四大部分,也就是我们常说的CRUD,不同的点只是传递的参数不同。
在使用MyBatis的时候,我们也可以向方法中传参,通过 #{ } 就可以拿到方法参数列表中的参数。
建议和参数名保持⼀致
/*** 根据ID查找用户* @param id* @return*/@Select("select username, `password`, age, gender, phone from userinfo where id= #{id} ")UserInfo queryById(Integer id);
如果参数名不一致,我们也可以通过注解的方式来实现需求,我们可以通过 @Param 设置参数的别名,如果使用 @Param 设置别名,#{...} 里面的属性名必须和 @Param 设置的⼀样
/*** 根据ID查找用户* @param id* @return*/@Select("select username, `password`, age, gender, phone from userinfo where id= #{userid} ")UserInfo queryById(@Param("userid") Integer id);
▐ 增(Insert)
SQL语句:
insert into userinfo (username, `password`, age, gender, phone) values ("zhaoliu","zhaoliu",19,1,"18700001234")
Mapper接口:
我们可以将对象作为参数,在调用的时候因为对象的字段和数据库的字段是一一对应的,所以可以直接通过对象里面的属性给SQL里面的字段赋值
/*** 插入用户* @param userInfo* @return*/@Insert("insert into userinfo (username, `password`, age, gender, phone) values (#{username},#{password},#{age},#{gender},#{phone})")Integer insert(UserInfo userInfo);
调用测试:
@Testvoid insert() {UserInfo userInfo = new UserInfo();userInfo.setUsername("zhaoliu");userInfo.setPassword("zhaoliu");userInfo.setGender(2);userInfo.setAge(21);userInfo.setPhone("18612340005");userInfoMapper.insert(userInfo);}
返回主键
insert语句默认返回的是受影响的行数,如果想要拿到⾃增id, 需要在Mapper接⼝的⽅法上添加⼀个Options的注解
/*** 插入用户信息,返回主键值* @param userInfo* @return*/@Options(useGeneratedKeys = true, keyProperty = "id")@Insert("insert into userinfo (username, `password`, age, gender, phone) values (#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender},#{userinfo.phone})")Integer insert(@Param("userinfo") UserInfo userInfo);
- useGeneratedKeys:这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据库内部⽣成的主键(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动递增字段),默认值:false.
- keyProperty:指定能够唯⼀识别对象的属性,MyBatis 会使⽤ getGeneratedKeys 的返回值或insert 语句的 selectKey ⼦元素设置它的值,默认值:未设置(unset)
测试
@Testvoid insert() {UserInfo userInfo = new UserInfo();userInfo.setUsername("zhaoliu");userInfo.setPassword("zhaoliu");userInfo.setGender(2);userInfo.setAge(21);userInfo.setPhone("18612340005");Integer count = userInfoMapper.insert(userInfo);System.out.println("添加数据条数:" +count +", 数据ID:" + userInfo.getId());}
注意: 设置 useGeneratedKeys=true 之后, ⽅法返回值依然是受影响的⾏数,⾃增id 会设置在上述 keyProperty 指定的属性中
▐ 删(Delete)
SQL语句:
delete from userinfo where id=6
Mapper接⼝:
/*** 根据ID删除用户* @param id*/@Delete("delete from userinfo where id = #{id}")void delete(Integer id);
▐ 改(Update)
SQL语句:
update userinfo set username="zhaoliu" where id=5
Mapper接口:
/*** 更新操作* @param userInfo*/@Update("update userinfo set username=#{username} where id=#{id}")void update(UserInfo userInfo);
▐ 查(Select)
查询语句在开发中占比非常大,在入门程序中我们已经体会了查询的基本用法,对于有几点需要额外注意
在数据库中的数据和实体类交互的时候,只有双方字段完全一模一样的时候,数据才会正常导入其中,在前面的数据库表和实体类中,其实有几个字段并没有做到完全的映射,这就导致了传入数据的时候往往结果是null
为了解决这样的问题,我们提供了以下三种解决方案
- 起别名
- 结果映射
- 开启驼峰命名
起别名
我们可以通过SQL语句中的 AS 来讲将数据库表字段和实体类字段进行一一对应,如下所示
/*** 查询全部用户(起别名)* @return*/@Select("select id, username, `password`, age, gender, phone, delete_flag as deleteFlag, " +"create_time as createTime, update_time as updateTime from userinfo")public List<UserInfo> queryAllUser();
PS:SQL语句太长可以用 " + "拼接
结果映射
通过@Results注解,我们可以将查询后的结果映射到实体类字段中
/*** 查询全部用户(结果映射)* @return*/@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from userinfo")@Results({@Result(column = "delete_flag",property = "deleteFlag"),@Result(column = "create_time",property = "createTime"),@Result(column = "update_time",property = "updateTime")})List<UserInfo> queryAllUser();
如果其他SQL, 也希望可以复⽤这个映射关系, 可以给这个Results定义⼀个名称
使⽤ id 属性给该 Results 定义别名, 使⽤ @ResultMap 注解来复⽤其他定义的 ResultMap
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from userinfo")@Results(id = "resultMap",value = {@Result(column = "delete_flag",property = "deleteFlag"),@Result(column = "create_time",property = "createTime"),@Result(column = "update_time",property = "updateTime")})List<UserInfo> queryAllUser();@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time " +"from userinfo where id= #{userid} ")@ResultMap(value = "resultMap")UserInfo queryById(@Param("userid") Integer id);
开启驼峰命名(推荐)
其实上述三种方法多多少少都是有点麻烦的,为了方便省事,我们可以通过配置项来解决问题
通常数据库列使⽤蛇形命名法进⾏命名(下划线分割各个单词),⽽ Java 属性⼀般遵循驼峰命名法约定,为了在这两种命名⽅式之间启⽤⾃动映射,需要将 mapUnderscoreToCamelCase 设置为 true。
mybatis:configuration:map-underscore-to-camel-case: true #配置驼峰⾃动转换
- 驼峰命名规则: abc_xyz => abcXyz
- 表中字段名:abc_xyz
- 类中属性名:abcXyz
而Java代码则不需要进行任何处理,使用这种方式需要注意实体类名称命名必须规范
三.MyBatis XML配置文件
对于一些较为复杂的SQL语句,写在注解里面会有很大程度的不方便,MyBatis为我们提供了通过XML文件映射的方式来实现SQL语句,将SQL语句和逻辑写在一个统一的XML文件中,当Java代码需要执行MyBatis的方法的时候,就会自动从这个XML文件中进行映射,然后拿到想要的SQL语句并且执行。
主要通过以下俩步进行完成
- 配置数据库连接字符串和MyBatis
- 写持久层代码
首先,需要在配置文件中配置XML文件的位置
mybatis:mapper-locations: classpath:mapper/*.xml
classpath 代表 resources 这个文件夹,即XML文件位置在 resources 下的 mapper 文件夹下的所有 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">
<mapper namespace="com.luming.mybatisdemo.mapper.UserInfoXMLMapper"></mapper>
前面俩行是固定格式,代表我们要使用 mybatis 中的这个功能,在<mapper>标签里面就是我们需要进行编码的地方,namespace 属性代表着这个XML文件对应的 Interface 接口的相对路径,可以直接复制其引用。
然后在其中,我们就可以通过不同的标签来实现不同的SQL,在具体的标签里面则只需要写具体的SQL语句,如下就是一个简单的查询全部用户信息的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"><mapper namespace="com.luming.mybatisdemo.mapper.UserInfoXMLMapper"><select id="queryAllUser" resultType="com.luming.mybatisdemo.pojo.UserInfo">select username,`password`, age, gender, phone from userinfo</select></mapper>
<select> 查询标签:是⽤来执⾏数据库的查询操作的:
- id :是和 Interface (接⼝)中定义的⽅法名称⼀样的,表⽰对接⼝的具体实现⽅法。
- resultType :是返回的数据类型,也就是我们定义的实体类(还是复制引用即可)
我们可以看到,XML文件和接口之间的对应关系(图中俩个小鸟),蓝色小鸟是接口,红色小鸟是XML文件。这是MyBatisX插件所提供的功能,供我们方便的跳转编码,还可以提供自动生成部分代码的功能。
通过XML来实现CRUD的各部分案例在下面给出
▐ 增(Insert)
UserInfoMapper接⼝
Integer insertUser(UserInfo userInfo);
UserInfoMapper.xml实现
<insert id="insertUser">insert into userinfo (username, `password`, age, gender, phone) values (#{username}, #{password}, #{age},#{gender},#{phone})</insert>
如果使⽤@Param设置参数名称的话, 使⽤⽅法和注解类似
Integer insertUser(@Param("userinfo") UserInfo userInfo);
<insert id="insertUser">insert into userinfo (username, `password`, age, gender, phone) values (#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender},#{userinfo.phone})</insert>
如果需要返回⾃增 id,接⼝定义不变 Mapper.xml 设置 useGeneratedKeys 和keyProperty属性即可
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">insert into userinfo (username, `password`, age, gender, phone) values (#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender},#{userinfo.phone})</insert>
▐ 删(Delete)
UserInfoMapper接⼝
Integer deleteUser(Integer id);
UserInfoMapper.xml实现
<delete id="deleteUser">delete from userinfo where id = #{id}</delete>
▐ 改(Update)
UserInfoMapper接⼝
Integer updateUser(UserInfo userInfo);
UserInfoMapper.xml实现
<update id="updateUser">update userinfo set username=#{username} where id=#{id}</update>
▐ 查(Select)
同样的, 使⽤XML 的⽅式进⾏查询, 也存在数据封装的问题,解决办法和注解类似:
- 起别名
- 结果映射
- 开启驼峰命名
其中起别名和驼峰命名和之前讲的完全一致,所以这里只会结果映射这一种方案讲解
<resultMap id="BaseMap" type="com.example.demo.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="queryAllUser" resultMap="BaseMap">select id, username,`password`, age, gender, phone, delete_flag,create_time, update_time from userinfo</select>
#{} 和 ${}
MyBatis 参数赋值有两种⽅式,即 #{ } 和 ${ } 。在JDBC中存在俩种SQL的执行操作,分别是直接执行SQL和预编译SQL。#{} 和 ${} 的区别就是预编译SQL和即时SQL 的区别。
#{ } 使⽤的是预编译SQL,通过 ? 占位的⽅式提前对SQL进⾏编译,然后把参数填充到SQL语句中, #{ } 会根据参数类型自动拼接引号 ' ' 。
${ } 会直接进⾏字符替换,⼀起对SQL进⾏编译,如果参数为字符串,需要加上引号 ' '。
当客⼾发送⼀条SQL语句给服务器后,⼤致流程如下:
- 解析语法和语义, 校验SQL语句是否正确
- 优化SQL语句, 制定执⾏计划
- 执⾏并返回结果
⼀条 SQL如果⾛上述流程处理, 我们称之为 Immediate Statements(即时 SQL)
绝⼤多数情况下,某⼀条 SQL 语句可能会被反复调⽤执⾏,或者每次执⾏的时候只有个别的值不同(⽐如 select 的 where ⼦句值不同, update 的 set ⼦句值不同, insert 的 values 值不同)。 如果每次都需要经过上⾯的语法解析,SQL优化、SQL编译等,则效率就明显不⾏了。
与上述相对应的就是预编译SQL,即编译⼀次之后会将编译后的SQL语句缓存起来,后⾯再次执⾏这条语句时,不会再次编译(只是输⼊的参数不同),省去了解析优化等过程,以此来提⾼效率。
同时预编译一定程度上还可以有效的防止了SQL注入的安全风险。
总的来说,#{ }可以有效的提高SQL执行效率,并且可以避免SQL注入的风险。
但这并不是说我们凡事就应该都用#{ },对于一些特殊的SQL语句可能会有对引号的要求,整个时候我们就需要谨慎使用了,如下进行排序查询的时候,由于SQL语法规定排序规则不需要加引号,所以这时就需要使用${ }。
@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " +"from userinfo order by id ${sort} ")
List<UserInfo> queryAllUserBySort(String sort);
@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " +"from userinfo order by id #{sort} ")
List<UserInfo> queryAllUserBySort(String sort);
本次的分享就到此为止了,希望我的分享能给您带来帮助,创作不易也欢迎大家三连支持,你们的点赞就是博主更新最大的动力!如有不同意见,欢迎评论区积极讨论交流,让我们一起学习进步!有相关问题也可以私信博主,评论区和私信都会认真查看的,我们下次再见