Spring(7)——MyBatis入门(1)
一、MyBatis入门
1.1 什么是MyBatis
MyBatis是一款优秀的持久层框架,用于简化JDBC的开发。
1.2 如何操作MyBatis
- 在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=1234
如果是application.yml文件,配置内容如下
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?
characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
-
Mapper接口
@Mapper public interface UserMapper { @Select("select id, name, age, gender, phone from user") public List<User> list(); }
Mybatis的持久层接⼝规范⼀般都叫xxxMapper
@Mapper注解:表示是MyBatis中的Mapper接⼝
程序运⾏时,框架会⾃动⽣成接⼝的实现类对象(代理对象),并给交Spring的IOC容器管理。
@Select注解:代表的就是select查询,也就是注解对应⽅法的具体实现内容。
1.3 数据库连接池
在没有数据库连接池时:没有使用数据库连接池:
客户端执行SQL语句:要先创建一个新的连接对象,然后执行SQL语句,SQL语句执行后又需要关闭连接对象从而释放资源,每次执行SQL时都需要创建连接、销毁链接,这种频繁的重复创建销毁的过程是比较耗费计算机的性能。
数据库连接池是个容器,负责分配、管理数据库连接(Connection)
程序在启动时,会在数据库连接池(容器)中,创建一定数量的Connection对象。
允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。
- 客户端在执行SQL时,先从连接池中获取一个Connection对象,然后在执行SQL语句,SQL语句执行完之后,释放Connection时就会把Connection对象归还给连接池(Connection对象可以复用)。
释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
- 客户端获取到Connection对象了,但是Connection对象并没有去访问数据库(处于空闲),数据库连接池发现Connection对象的空闲时间 > 连接池中预设的最大空闲时间,此时数据库连接池就会自动释放掉这个连接对象 。
1.4 日志输入
在Mybatis当中我们可以借助日志,查看到sql语句的执行、执行传递的参数以及执行结果。具体操作
如下:
- 打开application.properties文件
- 开启mybatis的日志,并指定输出到控制台
#指定mybatis输出日志的位置, 输出控制台
mybatis.configuration.logimpl=org.apache.ibatis.logging.stdout.StdOutImpl
开启日志之后,我们再次运行单元测试,可以看到在控制台中,输出了以下的SQL语句信息:
1.4.5 #{} 和${} 的区别
#{}
:预编译占位符
- 作用:
#{}
会将参数值作为预编译参数传递给数据库,类似于 JDBC 中的PreparedStatement
。 - 特点:
- 安全性高:能够有效防止 SQL 注入。
- 自动类型转换:MyBatis 会根据参数类型自动处理数据类型(如字符串添加引号)。
- 预编译:SQL 语句会被预编译,参数值以安全的方式插入。
示例
<select id="getById" resultType="Emp">
SELECT * FROM emp WHERE id = #{id}
</select>
生成的 SQL:
SELECT * FROM emp WHERE id = ?
参数值会通过 PreparedStatement
安全地传递给数据库。
${}
:字符串替换占位符
- 作用:
${}
会直接将参数值替换到 SQL 语句中,类似于字符串拼接。 - 特点:
- 灵活性高:可以用于动态拼接 SQL 片段(如表名、列名)。
- 安全性低:直接拼接字符串,容易导致 SQL 注入。
- 无自动类型转换:参数值会直接替换到 SQL 中,字符串需要手动添加引号。
示例
<select id="getByColumn" resultType="Emp">
SELECT * FROM emp WHERE ${column} = #{value}
</select>
生成的 SQL:
SELECT * FROM emp WHERE name = 'John'
如果 ${column}
是用户输入的,可能会导致 SQL 注入。
#{}
和${}
的对比
特性 | #{} | ${} |
---|---|---|
参数处理方式 | 预编译参数(PreparedStatement ) | 直接字符串替换 |
安全性 | 高,防止 SQL 注入 | 低,容易导致 SQL 注入 |
自动类型转换 | 是,自动处理数据类型 | 否,需要手动处理 |
适用场景 | 参数值传递(如 WHERE id = #{id} ) | 动态 SQL 片段(如表名、列名) |
1.5 CRUD
1.5.1 删除
@Mapper
public interface EmpMapper {
@Delete("delete from emp where id = #{id}")
public void delete(Integer id);
}
测试:
@SpringBootTest
class QuickMybatisApplicationTests {
@Autowired
private EmpMapper empMapper;
@Test
public void testDelete(){
empMapper.delete(17);
}
}
1.5.2 插入
@Mapper
public interface EmpMapper {
@Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " +
"values (#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})")
public void insert(Emp emp);
}
测试:
@SpringBootTest
class QuickMybatisApplicationTests {
@Autowired
private EmpMapper empMapper;
@Test
public void testInsert(){
//创建员工对象
Emp emp = new Emp();
emp .setUsername("tom");
emp.setName("汤姆");
emp.setImage("1.jpg");
emp.setGender((short)1);
emp.setJob((short)1);
emp.setEntrydate(LocalDate.of(2000,1,1));
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
emp.setDeptId(1);
//调用添加方法
empMapper.insert(emp);
}
}
1.5.3 主键返回
概念:在数据添加成功后,需要获取插入数据库数据的主键。
//会自动将生成的主键值,赋值给emp对象的id属性
@Options(useGeneratedKeys = true,keyProperty = "id")
@Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " +
"values (#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})")
public void insert2(Emp emp);
默认情况下,执行插入操作时,是不会主键值返回的。如果我们想要拿到主键值,需要在Mapper接口中的方法上添加一个Options注解,并在注解中指定属性useGeneratedKeys=true和keyProperty=“实体类属性名”。
1.5.4 更新
@Mapper
public interface EmpMapper {
@Update("update emp set username=#{username}, name=#{name}, gender=#{gender}, image=#{image}, " +
"job=#{job}, entrydate=#{entrydate}, dept_id=#{deptId}, update_time=#{updateTime} where id=#{id}")
public void update(Emp emp);
}
测试:
@Test
public void testUpdate(){
//要修改的员工信息
Emp emp = new Emp();
emp.setId(23);
emp.setUsername("songdaxia");
emp.setPassword(null);
emp.setName("老宋");
emp.setImage("2.jpg");
emp.setGender((short)1);
emp.setJob((short)2);
emp.setEntrydate(LocalDate.of(2012,1,1));
emp.setCreateTime(null);
emp.setUpdateTime(LocalDateTime.now());
emp.setDeptId(2);
emp.setId(1);
//调用方法,修改员工数据
empMapper.update(emp);
}
1.5.5 查询
@Select("select id, username, password, name, gender, image, job, " +
"entrydate, dept_id, create_time, update_time from emp where id=#{id}")
public Emp getById(Integer id);
测试:
@Test
public void get(){
Emp emp = empMapper.getById(2);
System.out.println(emp);
}
我们看到查询返回的结果中大部分字段是有值的,但是deptId,createTime,updateTime这几个字段是没有值的,而数据库中是有对应的字段值的,这是为什么呢?
原因如下:
实体类属性名和数据库表查询返回的字段名一致,mybatis会自动封装。
如果实体类属性名和数据库表查询返回的字段名不一致,不能自动封装。
解决方案:
- 起别名
- 结果映射
- 开启驼峰命名
起别名:在SQL语句中,对不一样的列名起别名,别名和实体类属性名一样 。
@Select("select id, username, password, name, gender, image, job, entrydate, " +
"dept_id AS deptId, create_time AS createTime, update_time AS updateTime " +
"from emp where id=#{id}")
public Emp getById2(Integer id);
手动结果映射:通过 @Results及@Result 进行手动结果映射
@Results({
@Result(column = "dept_id", property = "deptId"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "update_time", property = "updateTime")
})
@Select("select id, username, password, name, gender, image, job, " +
"entrydate, dept_id, create_time, update_time from emp where id=#{id}")
public Emp getById3(Integer id);
开启驼峰命名(推荐):如果字段名与属性名符合驼峰命名规则,mybatis会自动通过驼峰命名规则映射 。
# 在application.properties中添加:
mybatis.configuration.map-underscore-to-camel-case=true
要使用驼峰命名前提是 实体类的属性 与 数据库表中的字段名严格遵守驼峰命名。
条件查询
对于sql包含模糊查询和范围查询的情况:
select id, username, password, name, gender, image, job, entrydate,
dept_id, create_time, update_time
from emp
where name like '%张%'
and gender = 1
and entrydate between '2010-01-01' and '2020-01-01 '
order by update_time desc;
方式一:
@Select("select * from emp " +
"where name like '%${name}%' " +
"and gender = #{gender} " +
"and entrydate between #{begin} and #{end} " +
"order by update_time desc")
public List<Emp> list(String name, Short gender, LocalDate begin,
LocalDate end);
以上方式注意事项:
- 方法中的形参名和SQL语句中的参数占位符名保持一致
- 模糊查询使用${…}进行字符串拼接,这种方式呢,由于是字符串拼接,并不是预编译的形式,所以效率不高、且存在sql注入风险。
方式二:
使用MySQL提供的字符串拼接函数:concat(‘%’ , ‘关键字’ , ‘%’)
@Select("select * from emp " +
"where name like concat('%',#{name},'%') " +
"and gender = #{gender} " +
"and entrydate between #{begin} and #{end} " +
"order by update_time desc")
public List<Emp> list2(String name, Short gender, LocalDate
begin, LocalDate end);
执行结果:生成的SQL都是预编译的SQL语句 。