MyBatis 基础
目录
前言
一、MyBatis 入门操作
1. 引入依赖
2. 配置数据库连接
3. 编写持久层代码
4. 单元测试
二、MaBatis 基础操作
1. 打印日志
2. 参数传递
3. 增删改
4. 查询
1. 起别名
2. 结果映射
3. 开启驼峰命名
三、MyBatis XML 配置文件
1. 持久层代码实现
2. 增删改
3. 查询
1. 起别名
2. 结果映射
3. 开启驼峰自动转换
4. 多表查询
四、#{} 和 ${}
1. #{} 和 ${} 的区别
2. ${} 的应用场景
1. 排序
2. 模糊查询
五、数据库连接池
前言
本文介绍了MyBatis持久层框架的基本使用。首先说明了MyBatis的入门操作,包括创建SpringBoot工程、配置数据库连接、编写持久层代码和单元测试。然后详细讲解了MyBatis的基础CRUD操作,包括参数传递、增删改查的实现,以及解决数据库字段与Java对象属性映射问题的三种方法:起别名、结果映射和开启驼峰命名。接着介绍了XML配置方式的使用,包括方法定义与实现的分离、多表查询的实现。还重点分析了#{}和${}两种参数赋值的区别,强调预编译SQL的安全性优势。最后简要说明了数据库连接池的作用和常用类型。全文通过代码示例展示了MyBatis的各项功能,为开发者提供了实用的技术参考。
一、MyBatis 入门操作
MyBatis 是一款优秀的持久层框架,在 JDBC 的基础上进行了封装,用于简化 JDBC 的开发;
持久层指的是持久化操作的层,通常指数据访问层,用来操作数据库;
1. 引入依赖
创建 Spring Boot 工程,引入 mybatis 依赖和 mysql 驱动包;
由于 mybatis 的数据存储和操作是在 mysql 中完成的,因此需要引入 mysql 的驱动包;
工程创建完成后,在自动在 pom.xml 文件中引入依赖;
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.5</version>
</dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope>
</dependency>
2. 配置数据库连接
spring: # 数据库配置datasource:url: jdbc:mysql://127.0.0.1:3308/mybatis_test?characterEncoding=utf8&useSSL=falseusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver
3. 编写持久层代码
model:
import lombok.Data;
import org.springframework.stereotype.Component;import java.util.Date;@Data
@Component
public class UserInfo {private int id;private String username;private String password;private int age;private int gender;private String phone;private int deleteFlag;private Date createTime;private Date updateTime;
}
mapper:
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.*;import java.util.List;@Mapper
public interface UserInfoMapper {@Select("select * from userinfo")List<UserInfo> getUserInfo();
}
4. 单元测试
自动生成测试类:
选择要测试的方法:
编写测试代码:
import com.example.demo.model.UserInfo;
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
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userInfoMapper;@Testvoid getUserInfo() {List<UserInfo> userInfoList = userInfoMapper.getUserInfo();System.out.println(userInfoList);}
}
注意:要加上 @SpringBootTest 注解,加载 Spring 运行环境;
二、MaBatis 基础操作
1. 打印日志
mybatis:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 配置打印 MyBatis日志
2. 参数传递
下面代码为例,如果要按照 id 获取用户信息,则必须把 id 将参数传入:
可以使用 #{参数} 的方式传参;
也可以使用 @Param() 设置 别名;
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.*;import java.util.List;@Mapper
public interface UserInfoMapper {@Select("select * from userinfo where id = #{id}")UserInfo getUserInfo1(Integer id);@Select("select * from userinfo where id = #{num}")UserInfo getUserInfo11(@Param("num") Integer id);
}
通常情况下,为了便于理解,起别名也会保持和对象的属性名称一致;
3. 增删改
mapper:
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.*;import java.util.List;@Mapper
public interface UserInfoMapper {@Insert("insert into userinfo (username, password, age, gender) " +"values (#{username}, #{password}, #{age}, #{gender})")Integer insert(UserInfo userInfo);@Options(useGeneratedKeys = true, keyProperty = "id")@Insert("insert into userinfo (username, password, age, gender) " +"values (#{info.username}, #{info.password}, #{info.age}, #{info.gender})")Integer insert1(@Param("info") UserInfo userInfo);@Delete("delete from userinfo where id = #{id}")Integer delete(Integer id);@Update("update userinfo set password = #{password}, age = #{age}, gender = #{gender} where id = #{id}")Integer update(UserInfo userInfo);
}
test:
import com.example.demo.model.UserInfo;
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
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userInfoMapper;@Testvoid insert() {UserInfo userInfo = new UserInfo();userInfo.setUsername("zhaoliu");userInfo.setPassword("666666");userInfo.setAge(18);userInfo.setGender(1);System.out.println(userInfoMapper.insert1(userInfo));}@Testvoid insert1() {UserInfo userInfo = new UserInfo();userInfo.setUsername("libai");userInfo.setPassword("000000");userInfo.setAge(15);userInfo.setGender(1);System.out.println("影响行数: " + userInfoMapper.insert(userInfo) + ", id: " + userInfo.getId());}@Testvoid delete() {System.out.println(userInfoMapper.delete(5));}@Testvoid update() {UserInfo userInfo = new UserInfo();userInfo.setPassword("123456");userInfo.setAge(22);userInfo.setGender(2);userInfo.setId(6);System.out.println(userInfoMapper.update(userInfo));}
}
注意:使用 useGenerateKeys=true 后,方法的返回值依然是受影响的行数,自增 id 会设置在上述的 keyProperty 指定的属性中;
4. 查询
查询的时候,某些字段无法查到,使用了默认值 null:
原因是数据库中的字段和 Java 对象中的属性没有完全对应上;
解决这个问题的办法有三种:起别名,结果映射,开启驼峰命名;
1. 起别名
数据库中和 Java 对象字段对应不上的列,通过起别名的方式和 Java 对象中的属性对应上:
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.*;import java.util.List;@Mapper
public interface UserInfoMapper {// 查询// 起别名@Select("select id, username, password, age, gender, phone, " +"delete_flag as deleteFlag, create_time as createTime, update_time as updateTime from userinfo")List<UserInfo> getUserInfo2();
}
2. 结果映射
使用 @Results 注解,里面包含多个 @Result 注解;
分别使用每个 @Result 注解指定数据库字段和 Java 对象属性的对应关系;
后续如果还使用到这个对应关系,可以给这个 @Results 定义一个名称,后续使用 @ResultMap 注解调用这个对应关系;
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.*;import java.util.List;@Mapper
public interface UserInfoMapper {// 结果映射@Results(id = "UserInfoResultMap", value = {@Result(column = "delete_flag", property = "deleteFlag"),@Result(column = "create_time", property = "createTime"),@Result(column = "update_time", property = "updateTime")})@Select("select * from userinfo")List<UserInfo> getUserInfo3();@Select("select id, username, password, delete_flag, create_time, update_time from userinfo")@ResultMap(value = "UserInfoResultMap")List<UserInfo> getUserInfo4();
}
3. 开启驼峰命名
map-underscore-to-camel-case: true #配置驼峰自动转换
数据库通常使用蛇形命名,Java 通常使用大驼峰进行命名,这个设置可以启动自动映射;
三、MyBatis XML 配置文件
MyBatis 的开发有两种方式,一种是使用注解,另外一种是使用 xml 方式;
上面是注解的使用方式,下面是 xml 的使用方式;
mybatis: # 配置 mybatis xml 的文件路径,在 resources/mybatis 创建所有表的 xml 文件mapper-locations: classpath:mybatis/**Mapper.xml
1. 持久层代码实现
持久层代码,分两个部分,一个是方法定义,另一个是方法实现;
使用注解的方式实际上也是分两个部分,一个是方法定义,一个是使用注解实现,只是方法和注解是写在一起的,xml 文件中是分开写的;
方法定义:
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapper
public interface UserInfoXmlMapper {List<UserInfo> getUserInfo();
}
方法实现:
<?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.example.demo.mapper.UserInfoXmlMapper"><select id="getUserInfo" resultType="com.example.demo.model.UserInfo">select * from userinfo</select>
</mapper>
注意:xml 文件中的 namespace 的值需要和方法定义的类的全限定类名一致,实现的 id 需要和方法名称一致;
2. 增删改
方法定义:
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapper
public interface UserInfoXmlMapper {Integer insertUser(UserInfo userInfo);Integer deleteUserById(Integer id);Integer updateUserById(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="com.example.demo.mapper.UserInfoXmlMapper"><insert id="insertUser" useGeneratedKeys="true" keyProperty="id">insert into userinfo (username, password, age, gender)values (#{username}, #{password}, #{age}, #{gender})</insert><delete id="deleteUserById">delete from userinfo where id = #{id}</delete><update id="updateUserById">update userinfo set password = #{password}, age = #{age}, gender = #{gender} where id = #{id}</update></mapper>
3. 查询
使用 xml 查询时,也会存在某些字段无法查到,显示默认值的问题;
同样也是 3 种方法:起别名,结果映射和驼峰转换;
1. 起别名
<?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.example.demo.mapper.UserInfoXmlMapper"><select id="getUserInfo2" resultType="com.example.demo.model.UserInfo">select id, username, password, age, gender, phone,delete_flag as deleteFlag, create_time as createTime, update_time as updateTime from userinfo</select></mapper>
2. 结果映射
<?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.example.demo.mapper.UserInfoXmlMapper"><resultMap id="UserInfoResultMap" 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="getUserInfo3" resultType="com.example.demo.model.UserInfo" resultMap="UserInfoResultMap">select id, username, password, age, gender, phone, delete_flag, create_time, update_time from userinfo</select></mapper>
3. 开启驼峰自动转换
mybatis: # 配置 mybatis xml 的文件路径,在 resources/mapper 创建所有表的 xml 文件mapper-locations: classpath:mybatis/**Mapper.xmlconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 配置打印 MyBatis日志map-underscore-to-camel-case: true #配置驼峰自动转换
4. 多表查询
model:
import lombok.Data;
import org.springframework.stereotype.Component;import java.util.Date;@Data
@Component
public class ArticleInfo {private int id;private String title;private String content;private int uid;private int deleteFlag;private Date createTime;private Date updateTime;// 补充用户相关信息private String username;private int age;private int gender;
}
mapper:
import com.example.demo.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface ArticleInfoXmlMapper {ArticleInfo getArticleInfoById(Integer id);
}
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.example.demo.mapper.ArticleInfoXmlMapper"><resultMap id="ArticleInfoResultMap" type="com.example.demo.model.ArticleInfo"><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="getArticleInfoById" resultType="com.example.demo.model.ArticleInfo" resultMap="ArticleInfoResultMap">select articleinfo.id, articleinfo.title, articleinfo.content, articleinfo.uid,articleinfo.delete_flag, articleinfo.create_time, articleinfo.update_time,userinfo.username, userinfo.age, userinfo.gender from articleinfo, userinfowhere articleinfo.uid = userinfo.idand articleinfo.uid = 1</select>
</mapper>
四、#{} 和 ${}
1. #{} 和 ${} 的区别
mybatis 有两种参数赋值的方式,一种是使用 #{} 进行赋值,另一种是使用 ${} 进行赋值;
方法定义和实现:
@Mapper
public interface UserInfoMapper {@Select("select id, username, password, age, gender, phone, " +"delete_flag, create_time, update_time from userinfo " +"where id = #{id}")UserInfo queryById1(Integer id);@Select("select id, username, password, age, gender, phone, " +"delete_flag, create_time, update_time from userinfo " +"where id = ${id}")UserInfo queryById2(Integer id);
}
单元测试:
import com.example.demo.model.UserInfo;
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
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userInfoMapper;@Testvoid queryById1() {System.out.println(userInfoMapper.queryById1(1));}@Testvoid queryById2() {System.out.println(userInfoMapper.queryById1(1));}
}
#{} 使用的是预编译 SQL,通过 ‘?’ 占位的方式,提前对 SQL 进行编译,然后把参数填充到 SQL 语句中,#{} 会根据参数的类型启动拼接引号;
${} 会直接进行字符替换,一起对 SQL 进行编译;如果参数为字符串,需要手动加上引号;
#{} 和 ${} 的区别本质上是预编译 SQL 和即时 SQL 的区别;
1. 性能方面
预编译 SQL 的性能更高,因为它编译一次会缓存起来,再执行这条语句时,不会再次编译,只会使用不同的参数,省去了解析和优化的过程,因此效率更高;
即时 SQL 每次都会执行解析和优化的过程,因此效率不如预编译 SQL;
2. 安全方面
#{} 只能写一个参数,如果进行 SQL 注入攻击,会识别到注入攻击的关键字而注入失败,比如如果注入 ‘ or 1 =1 -- ’,会识别到 or 关键字,注入不成功;
${} 因为是直接拼接,会有 SQL 注入的风险;
2. ${} 的应用场景
虽然 ${} 有 SQL 注入的风险,但是 ${} 也有存在的必要性;
1. 排序
排序时如果将 asc 或者 desc 作为参数传递,那么如果使用 #{} 就会默认加上引号,SQL 会执行失败,就需要用到 ${};
虽然也还是存在 SQL 注入的风险,但我们也可以通过校验参数的方式避免,因为排序只有 asc 和 desc 两种方式;
也可以写两个方法分别去实现升序和降序,不输入参数,避免 SQL 注入;
2. 模糊查询
使用 like 关键字模糊查询的时候,如果使用 #{}传参数,就会在字符串中增加引号,导致 SQL 执行失败,例如 ‘%’#{}‘%’;
如果使用 ${}, 就会有 SQL 注入的风险;
解决这个问题需要用到 SQL 的内置函数 concat(),concat('%',#{},‘%’),实现字符串的拼接,即可解决这个问题;
总之,能用 #{} 就用 #{},尽量不用 ${};
五、数据库连接池
数据库连接池负责分配、管理和释放数据库连接,允许应用程序重复使用一个现有的数据库连接,而不是反复创建和销毁连接;
反复创建和销毁连接,这样操作的开销是极大的,使用数据库连接池就避免了反复创建和销毁连接的过程;
目前常用的数据库连接池有 Hikari 和 Druid,Hikari 是 Spring 默认的数据库连接池;