MyBatis
Mybatis是什么
MyBatis 是一个开源、轻量级的数据持久化框架,是 JDBC 和 Hibernate 的替代方案。MyBatis 内部封装了 JDBC,简化了加载驱动、创建连接、创建 statement 等繁杂的过程,开发者只需要关注 SQL 语句本身。
数据持久化是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中数据模型的统称。例如,文件的存储、数据的读取以及对数据表的增删改查等都是数据持久化操作。
JavaEE三层架构:表现层、业务层、持久层
表现层:页面展示
业务层:逻辑处理
持久层:对数据持久化,将其保存到数据库
MyBatis 支持定制化 SQL、存储过程以及高级映射,可以在实体类和 SQL 语句之间建立映射关系,是一种半自动化的 ORM 实现。其封装性低于 Hibernate,但性能优秀、小巧、简单易学、应用广泛。
ORM(Object Relational Mapping,对象关系映射)是一种数据持久化技术,它在对象模型和关系型数据库之间建立起对应关系,并且提供了一种机制,通过 JavaBean 对象去操作数据库表中的数据。
MyBatis 的主要思想是将程序中的大量 SQL 语句剥离出来,使用 XML 文件或注解的方式实现 SQL 的灵活配置,将 SQL 语句与程序代码分离,在不修改程序代码的情况下,直接在配置文件中修改 SQL 语句。
MyBatis 与其它持久性框架最大的不同是,MyBatis 强调使用 SQL,而其它框架(例如 Hibernate)通常使用自定义查询语言,即 HQL(Hibernate查询语言)或 EJB QL(Enterprise JavaBeans查询语言)。
MyBatis 官方文档:https://mybatis.org/mybatis-3/zh/
优点
- MyBatis 是免费且开源的。
- 与 JDBC 相比,减少了 50% 以上的代码量。JDBC缺点:1.硬编码,改动参数时工作量巨大,代码维护性低2.操作繁琐,手动设置参数,手动封装结果集
- MyBatis 是最简单的持久化框架,小巧并且简单易学。
- MyBatis 相当灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL 写在 XML 中,和程序逻辑代码分离,降低耦合度,便于同一管理和优化,提高了代码的可重用性。
- 提供 XML 标签,支持编写动态 SQL 语句。
- 提供映射标签,支持对象与数据库的 ORM 字段关系映射。
- 支持存储过程。MyBatis 以存储过程的形式封装 SQL,可以将业务逻辑保留在数据库之外,增强应用程序的可移植性、更易于部署和测试。
缺点
- 编写 SQL 语句工作量较大,对开发人员编写 SQL 语句的功底有一定要求。
- SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
使用场景
MyBatis 专注于 SQL 本身,是一个足够灵活的 DAO 层解决方案。适用于性能要求高,且需求变化较多的项目,如互联网项目。
Mybatis快速入门
步骤
1.创建user表,添加数据
create database mybatis;
use mybatis;
drop table if exists tb_user;
create table tb_user(
id int primary key auto_increment,
username varchar(20),
password varchar(20),
gender char(1),
addr varchar(30)
);
INSERT INTO tb_user VALUES (1, 'zhangsan', '123', '男', '北京');
INSERT INTO tb_user VALUES (2, '李四', '234', '女', '天津');
INSERT INTO tb_user VALUES (3, '王五', '11', '男', '西安');
2.创建模块,导入坐标
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!-- mysql 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- 添加slf4j日志api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.20</version>
</dependency>
<!-- 添加logback-classic依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- 添加logback-core依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
将logback(Java 开源日志框架)的配置文件复制到resources中
3.编写MyBatis核心配置文件–>替换连接信息,解决硬编码问题
建议使用类路径下的资源文件进行配置,在resources中添加mybatis-config.xml
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
${}替换为相应的jdbc参数
4.编写SQL映射文件(mapper 映射器)–>统一管理SQL语句,解决硬编码问题
在resources中添加映射器配置文件,例子:UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace:名称空间-->
<mapper namespace="test">
<!--查询语句-->
<!--id:sql语句的唯一标识--><!--resultType:包装类的返回类型-->
<select id="selectAll" resultType="org.example.pojo.User">
select * from tb_user;
</select>
</mapper>
参数介绍:
id:sql语句的唯一标识
resultType:包装类的返回类型,填入包装类所在的位置
表示查询语句
select * from tb_user;即sql语句的内容
5.加载sql映射文件
进入mybatis-config.xml,找到
<mappers>
<!--加载sql映射文件-->
<mapper resource="UserMapper.xml"/>
</mappers>
由于mybatis-config.xml与UserMapper.xml在同级目录下,直接写映射器配置文件名称UserMapper.xml
6.编码
1定义pojo类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6UGjasmZ-1666955956659)(Mybatis.assets/image-20221028121105675.png)]
添加对应数据库表 中的私有变量,添加setter getter方法,添加toString方便显示。
2.加载核心配置文件,获取SqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
3.获取SqlSession对象,执行SQL语句
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> users = sqlSession.selectList("test.selectAll");//名称空间.id
System.out.println(users); //打印出来查询结果
4.释放资源
sqlSession.close();
IDEA连接数据库
点击最右侧Database
输入数据库名称(Database),User、password
OK
Mapper代理开发(重点)
需求分析:
执行SQL的语句中用了namespace.id来指定唯一的sql语句。这种形式仍存在硬编码问题。
而且后期执行sql还要去配置文件中手动找id
步骤
1.定义与SQL映射文件同名的Mapper接口,名且将Mapper接口和SQL映射文件放置在同一目录下
在resources中新建文件夹,名称为Mapper接口所在的包名称,将.改为\
例:UserMapper接口位于org.example.mapper包中,那么在resources中新建的文件名为org\example\mapper,将SQL映射文件拖入mapper中
2.加载SQL映射文件
此时由于UserMapper.xml配置文件位置移动到了和Mapper接口同一个目录下,所以也要更改mybatis-config.xml中的sql映射文件路径
- 老方法:
copy UserMapper.xml的Path From Source Root,paste到
-
包扫描方法:
由于此时mapper配置文件与mapper接口位于同一目录下,可使用包扫描方法。方便添加多个映射文件
<package name="org.example.mapper"/>
3.设置SQL映射文件的namespace属性为Mapper接口全限定名
<mapper namespace="org.example.mapper.UserMapper">
4.在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致
由于查询出的是多个User记录,所以类型为集合List。
selectAll()与sql语句的id相同
5.编码
执行sql步骤改为
//获取UserMapper接口的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用selectAll方法
List<User> users = userMapper.selectAll();
Mybatis核心配置文件
详情见官方文档
环境配置environemnts
配置数据库连接环境信息,可以配置多个environment,通过default属性切换到不同的environment
类型别名typeAliases
在<configuration></configuration>中配置typeAliases
<typeAliases>
<package name="org.example.pojo"/>
</typeAliases>
相当于给org.example.pojo下的类都起了个别名,默认情况下,sql映射文件中resultType的名称就是类名称(不区分大小写)
配置文件完成增删改查
准备环境
数据库表 tb_brand
实体类 Brand
测试用例
安装 MyBatisX 插件
MybatisX是一款基于IDEA的快速开发插件,为效率而生
安装完成后红色小鸟代表sql映射文件,蓝色小鸟代表Mapper接口
查询-查询所有数据
1.编写接口方法:Mapper接口
List<Brand> selectAll();
2.编写SQL语句:SQL映射文件
粘贴进Mapper配置文件中
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace:名称空间-->
<mapper namespace="org.example.mapper.BrandMapper">
</mapper>
点击小蓝鸟
跳转到BrandMapper接口,点击方法名,alt+enter,快速生成statement
填写要执行的sql语句
3.执行方法,测试
public class MyBatisTest {
@Test
public void testSelectAll() throws IOException {
//1.获取SqlSeeesionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//3.获取Mapper接口的代理对象
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//4.执行方法
List<Brand> brands = brandMapper.selectAll();
System.out.println(brands);
//5.释放资源
sqlSession.close();
}
}
发现问题:发现有些数据被封装了,有些数据没有被封装,查询结果为null
原因:数据库表的字段名称和实体类的属性名称不一样,不能自动封装
解决:
方式1:给数据库字段名称起别名,让别名和实体类属性名称一样。逻辑简单,但是操作麻烦。
方式2:定义sql片段
在名称空间内添加sql片段
<sql id="brand_column">
id, brand_name as brandName, company_name as companyName, ordered, description, status
</sql>
<select id="selectAll" resultType="org.example.mapper.BrandMapper">
select
<include refid="brand_column"/>
from tb_brand;
</select>
缺点:不灵活
方式3:resultMap(重点)
<resultMap id="brandResultMap" type="org.example.pojo.Brand">
<result column="brand_name" property="brandName"/>
<result column="company_name" property="companyName"/>
</resultMap>
<select id="selectAll" resultMap="brandResultMap">
select *
from tb_brand;
</select>
1.定义标签
- id:唯一标识 type:映射的类型,支持别名,相当于select标签中的resultType,填写对应的实体类的路径
- column:表的列名
- property:实体类中对应的属性名
2.在标签中,使用resultMap属性替换 resultType 属性
查询-查看详情
分析
查询一个表中指定id对应的详细记录
步骤
1.编写接口方法:Mapper接口
参数:id
结果:Brand
在BrandMapper接口中添加selectById方法
Brand selectById(int id);
2.编写SQL语句:SQL映射文件
alt+enter快速生成selectById方法对应的statement
<select id="selectById" resultMap="brandResultMap">
select *
from tb_brand where id = #{id};
</select>
-
参数占位符:
#{}: 会将其替换为 ? ,防止sql注入
${}:直接拼接上参数,会存在sql注入问题
使用时机:
- 参数传递的时候:#{}
- 表名或者列名不固定的情况下:${} 会存在SQL注入问题
详情:SpEL表达式总结 - 简书 (jianshu.com)
Spring高级之注解@PropertySource详解(超详细)_我会努力变强的的博客-CSDN博客_propertysource
-
参数类型:parameterType 可以省略
-
特殊字符处理:如<在xml文件中表示标签开始
- 转义字符
- CDATA区 <![CDATA[内容]]>
- 详情:CSDN转义字符https://blog.csdn.net/hbspring007/article/details/119816906
3.执行方法,测试
查询-多条件查询
分析需求
通过
- 当前状态 0或1
- 企业名称 模糊查询或正则
- 品牌名称 模糊查询或正则
三个条件查询
1.可能查询出不止一条记录,所以用List接收参数
2.条件如何连接?and
步骤
1.编写接口方法:Mapper接口
- 参数:所有查询条件
- 结果:List
方式1
使用 @Param(“参数名称”) 标记每一个参数,在映射配置文件中就需要使用 #{参数名称} 进行占位
List<Brand> selectByCondition(@Param("status") int status, @Param("companyName") String
companyName, @Param("brandName") String brandName);
方式2
将多个参数封装成一个 实体对象 ,将该实体对象作为接口的方法参数。该方式要求在映射配置文件的SQL中使用 #{内 容} 时,里面的内容必须和实体类属性名保持一致。
List<Brand> selectByCondition(Brand brand);
方式3
将多个参数封装到map集合中,将map集合作为接口的方法参数。该方式要求在映射配置文件的SQL中使用 #{内容} 时,里面的内容必须和map集合中键的名称一致。
List<Brand> selectByCondition(Map map)
2.编写SQL语句:SQL映射文件
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
where status = #{status}
and company_name like #{companyName}
and brand_name like #{brandName}
</select>
like 模糊匹配
3.执行方法,测试
值得注意的是,要对公司名和品牌名进行模糊处理,要处理参数
//接收参数
int status = 1;
String companyName = "华为";
String brandName = "华为";
//处理参数
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";
成功查询出华为和状态为1的记录
方式1
//4.执行方法
List<Brand> brands = brandMapper.selectByCondition(status,companyName,brandName);
System.out.println(brands);
方式2
//封装对象
Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);
//4.执行方法
List<Brand> brands = brandMapper.selectByCondition(brand);
System.out.println(brands);
方式3
Map map = new HashMap();
map.put("status",status);
map.put("companyName",companyName);
map.put("brandName",brandName);
List<Brand> brands = brandMapper.selectByCondition(map);
System.out.println(brands);
查询-多条件动态条件查询
问题
用户可能只想通过一个或两个条件查询。而我们的条件查询语句为三个条件查询。不灵活
解决
让SQL语句会随着用户的输入或外部条件的变化而变化,动态SQL
if条件标签
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
where
<if test="status != null">
status = #{status}
</if>
<if test="status != null">
and company_name like #{companyName}
</if>
<if test="status != null">
and brand_name like #{brandName}
</if>
</select>
问题
当用户传入后面的条件而没有传入第一个条件时,会造成语法错误。
解决
1.添加恒等式,如1=1,并让第一个条件的格式与其他条件一致(加and)
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
where 1=1
<if test="status != null">
and status = #{status}
</if>
<if test="status != null">
and company_name like #{companyName}
</if>
<if test="status != null">
and brand_name like #{brandName}
</if>
</select>
2.where标签
用替换where
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
<where>
<if test="status != null">
and status = #{status}
</if>
<if test="status != null">
and company_name like #{companyName}
</if>
<if test="status != null">
and brand_name like #{brandName}
</if>
</where>
</select>
查询-单条件动态查询
需求:用户从多个条件中选择一个条件进行查询
choose(when,otherwise):选择,类似于switch语句
<select id="selectByConditionSingle" resultMap="brandResultMap">
select *
from tb_brand
<where>
<choose><!--相当于switch-->
<when test="status != null"><!--相当于case-->
status = #{status}
</when>
<when test="companyName != null and companyName != '' "><!--相当于case-->
company_name like #{companyName}
</when>
<when test="brandName != null and brandName != ''"><!--相当于case-->
brand_name like #{brandName}
</when>
</choose>
</where>
</select>
#{companyName}
and brand_name like #{brandName}
### 查询-单条件动态查询
需求:用户从多个条件中选择一个条件进行查询
choose(when,otherwise):选择,类似于switch语句
select * from tb_brand
status = #{status}
company_name like #{companyName}
brand_name like #{brandName} ```