Mybatis基础
什么是MyBatis?
- MyBatis是一款优秀的持久层框架,用于简化JDBC开发
- MyBatis负责将数据保存到数据库的那一层代码
- JavaEE三层架构:表现层,业务层,持久层
JDBC缺点
MyBatis对JDBC的优化
原生MyBatis快速入门
**例子:**查询User表中的所有数据
- 创建一个User表,添加数据
- Maven创建模块,导入坐标
- 编写MyBatis 核心配置文件 → \to →替换数据库连接信息【解决硬编码问题】
- 编写SQL映射文件 → \to → 统一管理SQL语句【解决硬编码问题】
- 编码:
- 定义实体POJO类
- 加载核心配置文件,获取SqlSessionFactory对象
- 获取SqlSession对象,执行SQL语句
- 释放资源
创建一个mybatis-config.xml
配置文件
<?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="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--加载sql的映射文件-->
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
再创建一个SQL的Mapper映射文件【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像包一样区分开来
-->
<mapper namespace="test">
<select id="selectAll" resultType="com.itheima.pojo.User">
select * from tb_user;
</select>
</mapper>
主函数加载配置文件获取SqlSessionFactory
并创建SqlSession
对象执行sql
public static void main(String[] args) throws IOException {
System.out.println("MyBatis快速入门程序案例...");
//1. 加载MyBatis的核心配置文件,获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//3. 执行SQL语句,调用SqlSession对象的方法,并且将Mapper映射文件的命名空间+方法ID
List<User> users = sqlSession.selectList("test.selectAll");
System.out.println(users);
//4. 释放资源
sqlSession.close();
}
Mapper代理开发
目的
-
解决原生方式中的硬编码【需要依赖字符串的字面值】
//原生的方法 List<User> users = sqlSession.selectList("test.selectAll"); System.out.println(users); //使用Mapper代理之后的方法: //1. 获取接口代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //2. 执行方法,调用SQL语句 List<User> users = userMapper.selectAll();
-
简化后期执行SQL
操作实现
- 定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下
- 设置SQL映射文件的namespace属性为Mapper接口的全限名
- 在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致
- 编码
- 通过SqlSession的getMapper方法获取Mapper接口的代理对象
- 调用对应方法完成sql的执行
**Tips:**如果Mapper接口名称和SQL映射文件名称相同,并且在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载
核心配置文件
在配置文件mybatis-config.xml
文件中:配置文件包含了会深深影响MyBatis行为的设置和属性信息。配置文档的结构如下:
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- envirment(环境变量)
- transactionManager(事务管理器)【一般不用修改】
- dataSource(数据源)【一般不用修改】
- envirment(环境变量)
- databaseldProvider(数据库厂商表示)
- mappers(映射器)
-
Mapper属性:用于加载SQL的映射文件
<mappers> <!--加载sql的映射文件--> <!--<mapper resource="com/itheima/mapper/UserMapper.xml"/>--> <package name="com.itheima.mapper"/> </mappers>
-
environment属性:配置数据库连接环境信息,可以配置多个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="123456"/> </dataSource> </environment> </environments>
-
typeAliases属性:配置类型别名
<!-- 在没有配置类型别名的时候,写resultType的时候经常要写一大堆包名+类名 --> <select id="selectAll" resultType="com.itheima.pojo.User"> select * from tb_user; </select>
在
mybatis-config.xml
配置文件中设置别名之后,会扫描别名配置中的包下的所有类,并起别名【别名默认为类型不区分大小写】<!-- 配置别名,自动扫描包下的所有实体类 --> <typeAliases> <package name="com.itheima.pojo"/> </typeAliases> <!-- 配置别名之后,resulttype可以用别名来书写 --> <select id="selectAll" resultType="user"> select * from tb_user; </select>
细节:配置各个标签时,需要遵循前后顺序
Mapper代理开发快速入门
-
创建
mybatis-config.xml
文件配置数据库信息<?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> <typeAliases> <package name="com.itheima.pojo"/> </typeAliases> <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="123456"/> </dataSource> </environment> </environments> <mappers> <!--加载sql的映射文件--> <!--<mapper resource="com/itheima/mapper/UserMapper.xml"/>--> <package name="com.itheima.mapper"/> </mappers> </configuration>
-
创建一个Mapper接口文件【此处文件为
UserMapper
】package com.itheima.mapper; import com.itheima.pojo.User; import java.util.List; public interface UserMapper { //写入接口需要实现的方法 List<User> selectAll(); }
-
创建一个SQL映射文件【文件名:
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"> <mapper namespace="com.itheima.mapper.UserMapper"> <select id="selectAll" resultType="user"> select * from tb_user; </select> </mapper>
-
主函数实现:
package com.itheima; import com.itheima.mapper.UserMapper; import com.itheima.pojo.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; import java.util.List; 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对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //3.1 获取UserMapper接口的代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> users = userMapper.selectAll(); System.out.println(users); //4. 释放资源 sqlSession.close(); } }
查询数据&结果映射
数据库表的字段名称 和 实体类的属性名称不一样时,则不能自动封装数据
-
解决方法
-
起别名:对不一样的列名起别名,让别名和实体类的属性名一样
- 缺点:每次查询都要重新定义一次别名
<!-- 一般的查询的时候,若是数据库字段名称和Java实体类属性名称不一致的时候就无法自动封装 --> select * from tb_brand; <!-- 比如在数据库中,列名有brand_name,company_name而在Java实体类中属性为brandName和companyName此时就要别名: --> <select id="selectAll" resultType="brand"> select id,brand_name as brandName,company_name as companyName, ordered,description,status from tb_brand; </select>
当需要多次查询同样的属性列的时候,可以使用SQL片段:
<sql id="all_tabs"> id,brand_name as brandName,company_name as companyName,ordered,description,status </sql> <select id="selectAll" resultType="brand"> select <include refid="all_tabs" /> from tb_brand; </select>
-
使用
resultMap
- 定义
<resultMap>
标签 - 在
<select>
标签中,使用resultMap属性替换 resultType属性
<resultMap id="brandResultMap" type="brand"> <!-- id:完成主键字段的映射 column:表的列名 property:实体类的属性名 result:完成一般字段的映射 --> <result column="brand_name" property="brandName"/> <result column="company_name" property="companyName"/> </resultMap> <select id="selectAll" resultMap="brandResultMap"> select * from tb_brand; </select>
- 定义
-
案例一:查询详细信息
-
编写接口方法:Mapper接口
Brand selectById(int id);
-
参数:id
-
结果:Brand
-
-
编写SQL语句:SQL映射文件
<select id="selectById" parameterType="int" resultType="brand"> select * from tb_brand where id = #{id}; </select>
使用细节:
参数占位符:
#{}
:会将其替换为 ? ,为了防止SQL注入
${}
:拼接SQL,存在SQL注入问题
- 使用时机:参数传递的时候使用
#{}
- 表名和列名不固定的时候使用:
${}
参数类型:
parameterType
:可以省略<select id="selectById" parameterType="int" resultMao="brandResultType"> select * from tb_brand where id = #{id}; </select>
特殊字符处理:
转义字符:【转义字符比较少的时候建议使用】
比如在SQL语句: select * from 表 where 参数1 < 参数2 中,在XML配置文件中小于号是不允许出现的因此将上述SQL语句的小于号使用转义字符 select * from 表 where 参数1 < 参数2
CDATA区域:【转义字符比较多的时候建议使用】
CDATA区域的字符默认转义,因此SQL语句: select * from 表 where 参数1 <![CDATA[ < ]]> 参数2
案例二:条件查询
SQL语句设置多个参数有几种方式?
- 参数接收:
- 散装参数:需要使用
@Param("SQL中的参数占位符名称")
- 实体类封装参数:只需要保证SQL中参数名和实体类属性名对应上,即可设置成功
- map集合:只需要保证SQL中的参数名和map集合的键的名称对应上,即可设置成功
- 散装参数:需要使用
多条件查询
-
编写接口方法:Mapper接口
// 第一种写法: List<Brand> selectByCondition(@Param("status")int status, @Param("companyName")String companyName, @Param("brandName")String brandName); // 第二种写法: List<Brand> selectByCondition(Brand brand); // 第三种写法: List<Brand> selectByCondition(Map map);
-
参数:所有查询的条件【status、company_name、brand_name】
-
结果:List
-
-
编写SQL语句:SQL映射文件【相当于JDBC的Statement
<select id="selectByCondition" resultMap="brandResultMap"> select * from tb_brand where status = #{status} and company_name like #{companyName} and brand_name like #{brandName} </select>
多条件查询的缺点:一定要对每一个参数占位符都要设置值,否知参数值为null
如何改进?动态SQL
案例三:动态条件查询
-
SQL语句随着用户的输入或外部条件的变化而变化,称为动态SQL
<select id="selectByCondition" resultMap="brandResultMapr"> select * from tb_brand where status = #{status} and company_name = #{companyName} and brand_name = #{brandName} </select>
-
MyBatis对动态SQL有很强大的支持:【提供了如下的标签,方便动态SQL的实现】
if
:用于判断参数是否有值,使用test属性进行条件判断- 存在的问题:第一个条件不需要逻辑运算符
- 解决方案:
- 使用恒等式让所有条件格式都一样
<where>
标签替换where关键字
choose(when,otherwise)
trim(where,set)
foreach
对案例二的条件查询进行优化
<select id="selectByCondition" resultMap="brandResultMapr">
select *
from tb_brand
<where>
<if test="status != null">
status = #{status}
</if>
<if test="companyName != null and companyName != ''">
and company_name like #{companyName}
</if>
<if test="brandName != null and brandName != ''">
and brand_name like #{brandName}
</if>
</where>
</select>
查询-单条件动态查询
- 从多个条件中选择一个
- choose(when,otherwise):选择,类似Java中的switch语句
<select id="selectByConditionSingle" resultType="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>
案例四:添加
-
编写接口方法:Mapper接口
void add(Brand brand)
-
参数:处理id之外的所有数据
-
结果:void
-
编写SQL语句:SQL映射文件
<insert id="add"> insert into tb_brand(brand_name,company_name,ordered,description,status) values (#{brandName},#{companyName},#{orderd},#{description},#{status}); </insert>
注意:
进行插入操作之后,需要手动提交事务…
sqlSession.commit()
MyBatis事务
openSession()
:默认开启事务,进行增删改查操作后需要使用sqlSession.commit()
手动提交事务openSession(true)
:可以设置为自动提交事务(关闭事务)
添加-主键返回
数据添加成功之后,需要获取插入数据库的数据的主键的值
举个例子:
useGeneratedKeys=true
设置生成主键值
keyProperty="属性名"
将生成的主键值赋予该属性上
<insert id="addOrder" useGeneratedKeys="true" keyProperty="id">
insert into tb_order(payment,payment_type,status)
values (#{payment},#{paymentType},#{status});
</insert>
案例五:修改
<update id="updateInfo">
update tb_user
set username=#{username},
password = #{password},
gender = #{gender},
addr = #{addr}
where id = #{id};
</update>
动态字段的修改
<update id="updateInfo">
update tb_user
<set>
<if test="username != null and username != '' ">
username=#{username},
</if>
<if test="password != null and password != '' ">
password = #{password},
</if>
<if test="gender != null and gender != '' ">
gender = #{gender},
</if>
<if test="addr != null and addr != '' ">
addr = #{addr}
</if>
</set>
where id = #{id};
</update>
案例六:删除
删除单个
-
编写接口方法:Mapper接口
void deleteById(int id);
-
参数:处理id之外的所有数据
-
结果:void
-
编写SQL语句:SQL映射文件
<delete id="deleteById"> delete from tb_brand where id=#{id}; </delete>
批量删除
-
编写接口方法:Mapper接口
void deleteByIds(@Params("ids")int[] ids);
- 参数:id数组
- 结果:void
-
编写SQL语句:SQL映射文件
<delete id="deleteByIds"> delete from tb_brand where id in <foreach collection="ids" item="id" separator=","open="(" close=")"> #{id} </foreach> </delete>
细节注意:
mybatis会将数组参数,封装为一个Map集合
-
默认:array = 数组
<!--比如,在mapper接口中写如下接口 --> int deleteByIds(int[] ids); <!--则在XML映射文件中要如下书写 --> <foreach collection="array" item="id" separator=","open="(" close=")"> #{id} </foreach>
-
使用@Param注解改变map集合的默认key的名称
<!--使用@Param注解后,在mapper接口中写如下接口 --> int deleteByIds(@Param("ids")int[] ids); <!--则在XML映射文件中要如下书写 --> <foreach collection="ids" item="id" separator=","open="(" close=")"> #{id} </foreach>
-
参数传递
MyBatis提供了ParamNameResolver 类来进行参数封装
MyBatis接口方法中可以接收各种各样的参数,MyBatis底层对于这些参数进行不同的封装处理方式
单个参数:
-
POJO类型:直接使用,实体类属性名 和 参数占位符名称一致
-
Map集合:直接使用,键名 和 参数占位符名称 一致
-
Collection:封装为Map集合
map.put("collection",collection集合); map.put("arg0",collection集合);
-
List:封装为Map集合
map.put("collection",list集合); map.put("list",list集合); map.put("arg0",list集合);
-
Array:封装为Map集合
map.put("array",数组); map.put("arg0",数组);
-
其他类型:直接使用
多个参数:封装为Map集合,可以使用@Param注解,替换Map集合中默认的arg键名
不设置@Param的时候,传递参数之后底层实现为:
// 1. 比如在下述接口中传递两个参数并且不使用@Param注解
User addInfo(String username,String password);
// 2. 参数1为username,参数2为password,则Mybatis底层创建map集合并
map.put("arg0",参数值1);
map.put("param1",参数值1);
map.put("arg1",参数值2);
map.put("param2",参数值2);
// 1. 使用@Param之后,接口如下
User addInfo(@Param("名1")String username,@Param("名2")String password);
// 2. 参数1为username,参数2为password,则Mybatis底层创建map集合并
map.put("名1",参数值1);
map.put("param1",参数值1);
map.put("名2",参数值2);
map.put("param2",参数值2);
注解开发
使用注解开发会比配置文件开发更加方便
@Select("select * from tb_user where id = #{id}")
public User selectById(int id);
- 查询:
@Select
- 添加:
@Insert
- 修改:
@Update
- 删除:
@Delete
注解完成简单功能
配置文件完成复杂功能
- 使用注解来映射简单语句会使代码显的更加简洁,但对于稍微复杂一点的语句,Java注解就力不从心,还会让本就复杂的SQL语句更加混乱不堪。因此,当需要完成复杂的操作,最好使用XML来映射语句
- 选择何种方式来配置映射,以及认为是否要统一映射语句的定义形式取决于实际需求。因此可以自由的在基于注解和XML的语句映射方式间移植和切换