demo03-Mybatis
1. 认识Mybatis
1.1 Mybatis概念
MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发
持久层:
-
负责将数据到保存到数据库的那一层代码。
以后开发我们会将操作数据库的Java代码作为持久层。而Mybatis就是对jdbc代码进行了封装。
-
JavaEE三层架构:表现层、业务层、持久层
框架:
-
框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型
- 怎么理解半成品软件:
- 一个软件人家编写了一半,我拿过来在人家编写的基础上再进行编写,合起来就是一个成品软件了
- 怎么理解半成品软件:
-
在框架的基础之上构建软件编写更加高效、规范、通用、可扩展
- 高效:人家已经写好了一半,所以高效
- 规范、通用、可扩展:人家把模板写好了,剩下的细节由我们编写
举例给大家简单的解释一下什么是半成品软件。大家小时候应该在公园见过给石膏娃娃涂鸦
如下图所示有一个石膏娃娃,这个就是一个半成品。你可以在这个半成品的基础上进行不同颜色的涂鸦,但这个娃娃的骨骼构架和性别已经设定好了是不能改变的
了解了什么是Mybatis后,接下来说说以前 JDBC代码
的缺点以及Mybatis又是如何解决的。
1.2 JDBC 缺点
下面是 JDBC 代码,我们通过该代码分析都存在什么缺点:
-
硬编码
-
注册驱动、获取连接
上图标1的代码有很多字符串,而这些是连接数据库的四个基本信息,以后如果要将Mysql数据库换成其他的关系型数据库的话,这四个地方都需要修改,如果放在此处就意味着要修改我们的源代码。
- 修改源代码就意味着需要重新编译,打包,运行,造成代码维护性差
-
SQL语句
上图标2的代码。如果表结构发生变化,SQL语句就要进行更改。这也不方便后期的维护。
-
-
操作繁琐
-
手动设置参数
-
手动封装结果集
上图标4的代码是对查询到的数据进行封装,而这部分代码是没有什么技术含量,而且特别耗费时间的。
-
1.3 Mybatis 优化
- 硬编码可以配置到配置文件
- 操作繁琐的地方mybatis都自动完成
如图所示
Mybatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作
2. Mybatis快速入门
需求:查询user表中所有的数据
分析:
- 创建user表,添加数据
- 创建模块,导入坐标
- 编写 MyBatis 核心配置文件 – > 替换连接信息 解决硬编码问题
- 编写 SQL 映射文件 --> 统一管理sql语句,解决硬编码问题
- 编码
- 定义POJO类
- 加载核心配置文件,获取SqlSessionFactory对象
- 获取SqlSession对象,执行SQL语句并封装结果集对象
- 释放资源
-
创建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) ) ENGINE=INNODB DEFAULT CHARSET=utf8; INSERT INTO tb_user VALUES (1,'zhangsan','123','男','北京'); INSERT INTO tb_user VALUES (2,'李四','234','女','天津'); INSERT INTO tb_user VALUES (3,'王五','121','男','西安');
-
创建模块,导入坐标
在创建好的模块中的 pom.xml 配置文件中添加依赖的坐标
<dependencies> <!--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.46</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> </dependencies>
注意:需要在项目的 resources 目录下创建logback的配置文件
-
编写 MyBatis 核心配置文件 – > 替换连接信息 解决硬编码问题
在模块下的 resources 目录下创建mybatis的配置文件mybatis-config.xml,内容如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <package name="com.itheima.pojo"/> </typeAliases> <!-- environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!--数据库连接信息--> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <!--加载sql映射文件,因为mybatis-config.xml和UserMapper.xml是同级的,所以直接写UserMapper.xml即可--> <mapper resource="UserMapper.xml"/> </mappers> </configuration>
-
编写 SQL 映射文件 --> 统一管理sql语句,解决硬编码问题
在模块的 resources目录下创建映射配置文件 UserMapper.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="test"> <select id="selectAll" resultType="com.itheima.pojo.User"> select * from tb_user; </select> </mapper>
-
编码
-
在
com.itheima.pojo
包下创建 User类public class User { private int id; private String username; private String password; private String gender; private String addr; //省略了 setter 和 getter }
-
在
com.itheima
包下编写 MybatisDemo 测试类public class MyBatisDemo { public static void main(String[] args) throws IOException { //1. 加载mybatis的核心配置文件,获取 SqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //2. 获取SqlSession对象,用它来执行sql SqlSession sqlSession = sqlSessionFactory.openSession(); //3. 执行selectList方法后mybatis内部会做以前jdbc中的:1.执行SQL语句2.封装结果集对象 List<User> users = sqlSession.selectList("test.selectAll"); System.out.println(users); //4. 释放资源 sqlSession.close(); } }
-
-
第12行参数是一个字符串,该字符串必须是映射配置文件的namespace.id->针对该案例就是test.selectAll,这样做的目的是:
- 如果在OrderMapper.xml中也有一个id为selectAll的但是namespace为test2,这样我们就可以用test2.selectAll进行区分
解决SQL映射文件的警告提示:
在入门案例映射配置文件中存在报红的情况。问题如下:
- 产生的原因:Idea和数据库没有建立连接,不识别表信息。但是大家一定要记住,它并不影响程序的执行。
- 解决方式:在Idea中配置MySQL数据库连接。
IDEA中配置MySQL数据库连接
- 首先需要开启mysql服务
- 点击IDEA右边框的
Database
,在展开的界面点击+
选择Data Source
,再选择MySQL
- 在弹出的界面进行基本信息的填写
- 点击完成后就能看到如下界面
而此界面就和 navicat
工具一样可以进行数据库的操作。也可以编写SQL语句
顺便提一下,解决IDEA连接Mysql数据库之后,在Mapper.xml中编写sql语句不会自动提示表信息的解决办法:
- 打开settings搜索查找到SQL Dialects,将上面两个都选择成MySQL数据库
- 这样就编写SQL语句就可以出现提示
3. Mapper代理开发
3.1 Mapper代理开发概述
之前我们写的代码是基本使用方式,它也存在硬编码的问题,如下:
List<User> users = sqlSession.selectList("test.selectAll");
这里调用 selectList()
方法传递的参数是映射配置文件中的 namespace.id值。这样写也不便于后期的维护。如果使用 Mapper 代理方式(如下)则不存在硬编码问题。
//getMapper获取UserMapper的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectAll();
通过上面的描述可以看出 Mapper 代理方式的目的:
- 解决原生方式中的硬编码
- 简化后期执行SQL
- 使用硬编码方式时我们要查找应该调用sqlSession的哪个方法,就很麻烦
3.2 使用Mapper代理要求
使用Mapper代理方式,必须满足以下要求:
1.定义与SQL映射文件同名的Mapper接口,并且使Mapper接口和SQL映射文件编译后在同一目录下,首先我们在itheima包下创建mapper.UserMapper接口,至于怎么使Mapper接口和SQL映射文件编译后在同一目录下,有两种办法:
法一:
- 直接将UserMapper.xml拉到mapper包下:
- **在工程的pom.xml(如果父工程本身就是一个maven项目,那么在父工程或子工程的pom.xml都可以)**的build中配置resources,来防止我们资源导出失败的问题:maven默认资源配置是放在resources里面的,但是UserMapper.xml放在了java目录下,这样是导不出来的,所以需要手动配置资源过滤,使src/main/java下面的properties和xml能够被导出(代码不用背,网上复制)
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
- 执行compile命令重新编译项目,可以看到在项目的target\classes\com\itheima\mapper下SQL映射文件已经和Mapper接口的字节码文件在同一目录下了:
法二:
法一可行,但不建议用,因为真实开发中maven项目的资源文件和java代码不应该放在同一目录,否则不便于后期管理维护
- 先看一下resources目录下的资源文件在执行编译命令后会被存放在哪里,可以看到在项目目录下的target/classes下:
- 我们已经知道该目录下的com的多级目录下就是编译好的java字节码文件,那如果我在resources下创建一个com/itheima/mapper包并将UserMapper.xml放在该目录下,编译后SQL文件就会和Mapper接口的字节码文件在同一目录下了
- 注意在resources下new directory时如果像在java包下那样直接com.itheima.mapper结果是:会将.作为文件夹名称最后只会创建一个一级文件夹,我们应该用com/itheima/mapper创建多级目录
-
因为这里改了UserMapper.xml的文件位置,所以需要在mybatis-config.xml中重新指定加载的sql映射文件的位置:
<mappers> <mapper resource="com.itheima.mapper.UserMapper.xml"/> </mappers>
2.设置SQL映射文件的namespace属性为Mapper接口全限定名
<!--
namespace:名称空间。必须是对应接口的全限定名
-->
<mapper namespace="com.itheima.mapper.UserMapper">
3.在 Mapper 接口中定义方法,方法名就是QL映射文件中sql语句的id,并保持参数类型和返回值类型一致(如果返回的是一个集合,就要用List集合,如List)
3.3 案例代码实现
-
在
com.itheima.mapper
包下创建 UserMapper接口,代码如下:public interface UserMapper { List<User> selectAll(); }
-
在
resources
下创建com/itheima/mapper
目录,并在该目录下创建 UserMapper.xml 映射配置文件<!-- namespace:名称空间。必须是对应接口的全限定名 --> <mapper namespace="com.itheima.mapper.UserMapper"> <select id="selectAll" resultType="com.itheima.pojo.User"> select * from tb_user; </select> </mapper>
-
在
com.itheima
包下创建 MybatisDemo2 测试类,代码如下:public class MyBatisDemo2 { public static void main(String[] args) throws IOException { //1. 加载mybatis的核心配置文件,获取 SqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //2. 获取SqlSession对象,用它来执行sql SqlSession sqlSession = sqlSessionFactory.openSession(); //3 获取UserMapper接口的代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> users = userMapper.selectAll(); System.out.println(users); //4. 释放资源 sqlSession.close(); } }
注意:
如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载。也就是将核心配置文件的加载映射配置文件的配置修改为
<mappers>
<!--加载sql映射文件-->
<!-- <mapper resource="com/itheima/mapper/UserMapper.xml"/>-->
<!--Mapper代理方式-->
<package name="com.itheima.mapper"/>
</mappers>
4. 核心配置文件
4.1 多环境配置
在核心配置文件的 environments
标签中其实是可以配置多个 environment
,使用 id
给每段环境起名,在 environments
中使用 default='环境id'
来指定使用哪儿段配置
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>=
4.2 类型别名
在映射配置文件中的 resultType
属性需要配置数据封装的类型(类的全限定名)。而每次这样写是特别麻烦的,Mybatis 提供了 类型别名
(typeAliases) 可以简化这部分的书写。
首先需要现在核心配置文件中配置类型别名,也就意味着给pojo包下所有的类起了别名(别名就是类名),不区分大小写。内容如下:
<typeAliases>
<!--name属性的值是实体类所在包-->
<package name="com.itheima.pojo"/>
</typeAliases>
通过上述的配置,我们就可以简化映射配置文件中 resultType
属性值的编写
<mapper namespace="com.itheima.mapper.UserMapper">
<select id="selectAll" resultType="user">
select * from tb_user;
</select>
</mapper>
配置各个标签时需要遵循前后顺序,常用的几个标签的顺序:
外部文件
日志
别名
环境
注册sql映射文件
5. Mybatis其他注意事项
5.1 查询数据
5.1.1 有些数据没有封装成功
比如数据库中字段是brand_name而实体类中属性是brandName,就会造成这个数据封装不成功,解决办法有两种:
1.起别名:
<select id="selectAll" resultType="brand">
select brand_name as brandName from user;
</select>
这里我是省略了很多字段,实际开发中可能有很多字段需要查询,而且如果其他功能也需要查询这些子段,用这种方式就显得代码不够精炼,Mybatis提供了sql片段可以提高sql的复用性:
<sql id="brand_column">username as userName</sql>
<select id="selectAll" resultType="brand">
select <include refid="brand_column" /> from user;
</select>
- sql片段中的id可以随意设置,只要下面的refid=""与之对应即可
2.使用ResultMap:
<resultMap id="brandResultMap" type="brand">
<!--
id:完成主键字段的映射
column:表的列名
property:实体类的属性名
result:完成一般字段的映射
column:表的列名
property:实体类的属性名
-->
<result column="brand_name" property="brandName"/>
</resultMap>
-
在上面只需要定义 字段名 和 属性名 不一样的映射,而一样的则不需要专门定义出来
-
然后SQL语句即可正常编写(注意将resultType属性改为resultMap属性)
<select id="selectAll" resultMap="brandResultMap"> select brand_name from tb_brand; </select>
5.1.2 select标签其他细节
<select id="selectById" paramType="int" resultMap="brandResultMap">
select *
from tb_brand where id = #{id};
</select>
1.#{id}和${id}:
- #{id}会使用?进行占位,避免了sql注入
- ${id}底层使用的是 Statement,会存在SQL注入问题
2.paramType可以省略不写
3.sql语句中特殊字符:
<select id="selectById" paramType="int" resultMap="brandResultMap">
select *
from tb_brand where id < #{id};
</select>
上述代码的<会报错,因为<在xml中是特殊字符,解决办法有两种:
-
将特殊字符转义
<select id="selectById" paramType="int" resultMap="brandResultMap"> select * from tb_brand where id < #{id}; </select>
-
使用<![CDATA[内容]]>
<select id="selectById" paramType="int" resultMap="brandResultMap"> select * from tb_brand where id <![CDATA[ < ]]> #{id}; </select>
- 在敲代码时写了CD后idea就会自动补全
5.1.3 多条件查询
比如说前端有2个输入框进行多条件查询:status,bandName,其一我们要考虑怎么才能在后端进行多条件查询,其二要考虑:用户可能不会把品牌名输完整,所以bandName在后台实现时应该是模糊查询
1.怎么才能实现模糊查询呢:
-
将从前端接收到的参数brandName进行拼接字符串:
companyName = "%" + companyName + "%";
-
编写sql语句:
<select id="selectByCondition" resultMap="brandResultMap"> select * from tb_brand where status = #{status} and brand_name like #{brandName} </select>
2.SQL语句设置有多个参数有几种方式:
-
散装参数:需要使用@Param(“SQL中的参数占位符名称”)
- 在BrandMapper接口中添加:
List<Brand> selectByCondition(@Param("status") int status, @Param("brandName") String brandName);
-
实体类封装参数:需要SQL中的参数占位符名称和实体类属性名对应上
- 在BrandMapper接口中添加:
List<Brand> selectByCondition(Brand brand);
- 在测试代码中对前端传来的数据进行封装并调用方法:
Brand brand = new Brand(); brand.setStatus(status); brand.setBrandName(brandName); List<Brand> brands = brandMapper.selectByCondition(brand);
-
map集合:需要SQL中的参数占位符名称和map集合的键的名称对应上
- 在BrandMapper接口中添加:
List<Brand> selectByCondition(Map map);
- 在测试代码中将前端传来的数据封装为map集合并调用方法:
Map map = new HashMap(); map.put("status" , status); map.put("brandName" , brandName); List<Brand> brands = brandMapper.selectByCondition(map);
5.1.4 动态SQL
5.1.4.1 多条件动态查询
如果前端的2个输入框用户只输入了一个,那么按照2.5.3的处理方式结果一个也查询不到,这显然是不符合业务需求的,接下来我们用动态SQL一步一步进行改进:
给sql语句加上if标签;
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
where
<if test="status != null">
status = #{status}
</if>
<if test="brandName != null and brandName != '' ">
and brand_name like #{brandName}
</if>
</select>
注意:brandName是前端传来的字符串,除了判断不为null,还需要判断不为空字符串
改进后的动态SQL语句,如果用户只输入了status那么拼接后的SQL就是:
select * from tb_brand where status = ?
这样的话如果用户只输入了status代码仍能正常执行,但是有个问题:如果用户只输入了brandName那么拼接后的SQL就变为:
select * from tb_brand where and brand_name like ?
where后面多了一个and,这条sql语句显然不能正常执行,针对这种情况有两种解决办法:
-
给sql加上恒等式:
- 在where的条件查询中加上1=1并在status = #{status}前面也加一个and
<select id="selectByCondition" resultMap="brandResultMap"> select * from tb_brand where 1=1 <if test="status != null"> and status = #{status} </if> <if test="brandName != null and brandName != '' "> and brand_name like #{brandName} </if> </select>
这样的话即使用户只输入了brandName那么拼接后的SQL就变为:
select * from tb_brand where 1=1 and brand_name like ?
可以知道sql语句仍能正常执行
-
将sql的where关键字改为where标签:
- 将sql语句中的where关键字替换为where标签,该标签会自动删掉sql语句中where关键字后的第一个and或or
<select id="selectByCondition" resultMap="brandResultMap"> select * from tb_brand <where> <if test="status != null"> and status = #{status} </if> <if test="brandName != null and brandName != '' "> and brand_name like #{brandName} </if> </where> </select>
如果用户只输入了brandName那么拼接后的SQL就变为:
select * from tb_brand where brand_name like ?
可以知道sql语句仍能正常执行
5.1.4.1 单条件动态查询
如上图所示,在查询时只能选择 品牌名称
、当前状态
、企业名称
这三个条件中的一个,但是用户到底选择哪儿一个,我们并不能确定。这种就属于单个条件的动态SQL语句。
这种需求需要使用到 choose(when,otherwise)标签
实现, 而 choose
标签类似于Java 中的switch语句。
1.先在BrandMapper接口中编写抽象方法:
- 因为我们不知道用户传的是哪个参数,所以散装参数是不能用的了,我们可以用实体类封装参数或map集合来解决
- 实体类封装参数:将传的这唯一一个参数对应的属性设值,其他的属性值都是默认值null
- map集合:只设置传过来的这一个参数对应的键值对,其他的键值对中的值都手动设为null
- 两种方法用法差别不大,但map集合稍麻烦点,建议使用"实体类封装参数"
List<Brand> selectByConditionSingle(Brand brand);
2.在 BrandMapper.xml映射配置文件中编写 statement,使用 resultMap 而不是使用 resultType
<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 != '' ">
company_name like #{companyName}
</when>
<when test="brandName != null and brandName != ''">
brand_name like #{brandName}
</when>
<otherwise><!--相当于default-->
1=1
</otherwise>
</choose>
</select>
这样的话如果用户查询的是brandName,那么sql就会拼接为:
select * from tb_brand where brand_name like ?
因为有otherwise的存在,所以即使用户什么也不输入该sql语句仍能正常执行:
select * from tb_brand where 1=1
还有一种办法:使用Mybatis提供的where标签(将where关键字改为where标签):
- 当where后面没东西时就会自动省略where关键字:
<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 != '' ">
company_name like #{companyName}
</when>
<when test="brandName != null and brandName != ''">
brand_name like #{brandName}
</when>
</choose>
</where>
</select>
如果用户什么也不输入该sql语句则会拼接为:
select * from tb_brand
5.2 添加数据
5.2.1 提交事务
Mybatis会为我们自动将"autocommit(自动提交事务)"设为false(也就是使用Mybatis时默认是关闭事务的),解决办法有两种:
- 在增删改时我们需要手动提交事务:在单元测试用例中调用完BrandMapper接口的增删改方法后再写一行代码用来提交事务:
SqlSession.commit();
- 获取SqlSession对象时给方法传参true(目的是开启自动提交事务,即设置为开启事务):
SqlSession sqlSession = sqlSessionFactory.openSession(true);
5.2.2 主键返回
数据添加成功后,有时候需要获取插入数据库数据的主键(主键是自增长)
<insert id="add" useGeneratedKeys="true" keyProperty="id">
insert into......
</insert>
- useGeneratedKeys:是够获取自动增长的主键值。true表示获取
- keyProperty :指定将获取到的主键值封装到实体类的哪个属性里
5.3修改数据
5.3.1 动态SQL
<update id="update">
update tb_brand
<set>
<if test="brandName != null and brandName != ''">
brand_name = #{brandName},
</if>
<if test="status != null">
status = #{status}
</if>
</set>
where id = #{id};
</update>
如果仍用Set关键字编写sql语句,有两个错误:
-
如果用户修改的值只有brandName那么拼接后的sql语句就会在where关键字之前多一个逗号:
update tb_brand set brand_name = ?, where id = ?;
-
如果用户一个值也不修改而是直接提交数据那么拼接后后的sql就会报错:
update tb_brand set where id = ?;
显然这也是错误的
-
使用set标签可以规避这两个错误
5.4 删除数据
5.4.1 动态SQL
这里说的是批量删除:
1.先在BrandMapper接口中添加抽象方法:
void deleteByIds(int[] ids);
2.在BrandMapper.xml中编写sql映射:
<delete id="deleteByIds">
delete from tb_brand where id
in (
<foreach collection="array" item="id" separator=",">
#{id}
</foreach>
);
</delete>
- collection 属性:
- mybatis会将数组参数,封装为一个Map集合
- 默认情况下,键是array,值是数组中数据
- 可以使用@Param注解改变map集合的默认key的名称
- mybatis会将数组参数,封装为一个Map集合
- item 属性:本次迭代获取到的元素
- separator 属性:集合项迭代之间的分隔符。
foreach
标签不会错误地添加多余的分隔符。也就是最后一次迭代不会加分隔符 - open 属性:该属性值是在拼接SQL语句之前拼接的语句,只会拼接一次
- close 属性:该属性值是在拼接SQL语句拼接后拼接的语句,只会拼接一次
根据上面的描述我们可以做一下两点改变(这里只是说一下,实际开发):
- 使用@Param注解修改map集合中的key的名称
- 使用open和close属性使其自动添加括号
void deleteByIds(@Param("ids") int[] ids);
<delete id="deleteByIds">
delete from tb_brand where id
in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
;
</delete>
5.5 参数传递
Mybatis 接口方法中可以接收各种各样的参数,如下:
- 多个参数
- 单个参数:单个参数又可以是如下类型
- POJO 类型:直接使用,属性名和参数占位符名称一致
- Map 集合类型:直接使用,键名和参数占位符名称一致
- Collection 集合类型:封装为Map集合
- map.put(“arg0”,collection集合的值)
- map.put(“collection”,collection集合的值)
- 但还是建议用@Param注解替换Map集合中默认的arg键名
- List 集合类型:封装为Map集合
- map.put(“arg0”,list集合的值)
- map.put(“collection”,list集合的值)
- map.put(“list”,list集合的值)
- 但还是建议用@Param注解替换Map集合中默认的arg键名
- Array 类型:封装为Map集合
- map.put(“arg0”,数组的值)
- map.put(“array”,数组的值)
- 但还是建议用@Param注解替换Map集合中默认的arg键名
- 其他类型:如int或String等等,直接使用即可,且参数占位符名称随便写都可以(如果用了@Param那应该就不能随便写了,改天试试)
接收多个参数需要使用 @Param 注解,那么为什么要加该注解呢(Mybatis提供了ParamNameResolver类来进行参数封装,具体原理可以去这个类的源代码,单个参数的默认key名称原理也要看源码,我目前还没看懂):
如果参数是多个,那么Mybatis就会自动将参数封装为map集合,且给集合的key名称设为默认值:
-
以 arg 开头 :第一个参数就叫 arg0,第二个参数就叫 arg1,以此类推。如:
map.put(“arg0”,参数值1);
map.put(“arg1”,参数值2);
-
以 param 开头 : 第一个参数就叫 param1,第二个参数就叫 param2,依次类推。如:
map.put(“param1”,参数值1);
map.put(“param2”,参数值2);
所以说如果传入的是两个参数那么map集合中就有四对键值对.
当使用@Param注解后,Mybatis 会将 arg开头的键名替换为对应注解的属性值(为了提高代码可读性,开发中还是写注解比较好)
5.6 注解开发
注解完成简单功能,配置文件完成复杂功能
@Select(select * from tb_user where id = #{id})
User selectById(int id)
查询:@Select
添加:@Insert
修改:@Update
删除:@Delete
5.7 SqlSessionFactory工具类抽取
在日后写Servlet的时候(可以等到看完Servlet再回来看这个),因为需要使用Mybatis来完成数据库的操作,所以对于Mybatis的基础操作就出现了些重复代码,如下
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new
SqlSessionFactoryBuilder().build(inputStream);
有了这些重复代码就会造成一些问题:
- 重复代码不利于后期的维护
- SqlSessionFactory工厂类进行重复创建
- 就相当于每次买手机都需要重新创建一个手机生产工厂来给你制造一个手机一样,资源消耗非常大但性能却非常低。所以这么做是不允许的。
那如何来优化呢?
- 代码重复可以抽取工具类
- 对指定代码只需要执行一次可以使用静态代码块
public class SqlSessionFactoryUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
//静态代码块会随着类的加载而自动执行,且只执行一次
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSessionFactory getSqlSessionFactory(){
return sqlSessionFactory;
}
}
工具类抽取以后,以后在对Mybatis的SqlSession进行操作的时候,就可以直接使用
SqlSessionFactory sqlSessionFactory =SqlSessionFactoryUtils.getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
从工具类中获取SqlSession的这行代码也是重复的,为什么不能抽取到工具类中呢:
- 每个SqlSession就代表用户和数据库的连接,如果抽取出来,将来所有用户所有功能都用这一个SqlSession,结果是不能再管理事务了,并且多个用户多个功能之间还会产生影响