什么是MyBatis?
MyBatis是一款优秀的持久层1框架2,用于简化JDBC开发。MyBatis本是Apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github
官网:https://mybatis.org/mybatis-3/
mybatis简化jdbc:MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qeXR5ccb-1721137208680)(https://i-blog.csdnimg.cn/direct/3187e85170c54319b80c31d061d1c7ed.png#pic_center)]
一、mybatis快速入门
需求:查询emp表中所有数据
1.创建emp表,添加数据
2.创建模块,***pom.xml***文件中导入坐标
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
3.编写mybatis核心配置文件mybatis-config.xml----》替换连接信息 解决硬编码问题
- 之前连接信息是写在Java代码中的,现在改到配置文件里去,可以解决硬编码问题。
<?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.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///javaweb02"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--添加sql映射文件,此时核心配置文件与sql映射文件在同一文件夹下-->
<mapper resource="EmpMapper.xml"/>
</mappers>
</configuration>
4.编写sql映射文件EmpMapper.xml —》 统一管理sql语句,解决硬编码问题
<?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="EmpMapper">
<!--id:此sql语句唯一标识 resultType:全类名,实体类-->
<select id="findAllEmp" resultType="org.example.pojo.Emp">
select * from emp
</select>
</mapper>
5.编码
- 1.定义pojo类,根据数据库中emp表创建出对应的Emp实体类
- 2.创建mybatis测试类,加载核心配置文件,获取SqlSessionFactory对象
- 3.通过SqlSessionFactory获取SqlSession对象,执行sql语句,
- 4.释放资源
public class EmpTest {
public static void main(String[] args) throws IOException {
// 2.加载核心配置文件,获取SqlSessionFactory对象
String resource = "mybatis-config.xml";
// 使用mybatis中的工具类Resources
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建sqlSessionFactory工厂类对象,
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 使用工厂类对象sqlSessionFactory创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3.使用sqlSession对象,调用selectList()方法,执行sql语句,
// 传入sql语句的唯一标识(名称空间+id)
List<Object> emps = sqlSession.selectList("EmpMapper.findAllEmp");
//selectList()方法中的参数也属于硬编码,
//想要解决这个问题,请接着看Mapper代理👇
//遍历打印
emps.forEach(emp -> System.out.println(emp));
// 4.释放资源
sqlSession.close();
}
}
二、Mapper代理开发
目的:解决原生方式中的硬编码,简化后期执行sql。 mybatisX插件
使用Mapper代理方式完成入门案例
1.定义与sql映射文件同名的Mapper***接口***,并且将Mapper接口和SQL映射文件放置在同一目录下(编译后)
-
使用maven进行编译后我们发现,在target/classes中存在xml文件,但是要想将mapper接口与sql映射文件放在同一文件夹下需要更改sql映射文件的放置路径,这样的话在编译完成后Mapper接口和sql映射文件将会放到同一文件夹下。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rNgxjSec-1721137208688)(https://i-blog.csdnimg.cn/direct/94cc3ed3f731489a8151149f634d8022.png#pic_center)] -
注意:EmpMapper应为接口
Q:mapper接口和sql映射文件为什么要放在同一文件夹下?
A:Mapper接口和SQL映射文件通常放在同一文件夹下的原因是为了方便MyBatis框架的自动扫描和配置。在MyBatis中,可以通过配置来告诉框架在哪里查找Mapper接口及其对应的XML映射文件。如果这些文件位于同一目录下,并且接口的名称与XML文件的名称相匹配,MyBatis可以自动关联它们,无需额外的配置。这样做可以提高开发效率,减少配置错误,并使得项目结构更加清晰
2.设置SQL映射文件的namespace属性的值为Mapper接口全限定名
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Kipo5u5-1721137208691)(https://i-blog.csdnimg.cn/direct/5011cc02657a416e99fed944b5525dd0.png#pic_center)]
3.在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持resultType参数类型和方法的返回值类型一致
public interface EmpMapper {
public List<Emp> findAllEmp();
}
4.编码
- 1.通过SqlSession的getMapper方法获取Mapper接口的代理对象
- 2.调用对应方法完成sql的执行
@Test
public void testMapper() throws IOException {
//1.使用自定义工具类MybatisUtils,获取sqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//2.使用sqlSession获取mapper代理对象
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//3.使用mapper代理对象调用接口方法,执行sql
List<Emp> allEmp = mapper.findAllEmp();
//遍历输出
allEmp.forEach(emp -> System.out.println(emp));
//4.关闭sqlSession
sqlSession.close();
}
细节:如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,那么sql核心配置文件中则可以使用包扫描的方式简化SQL映射文件的加载
<mappers>
<!--1.sql映射文件,映射单个文件-->
<!--<mapper resource="org/example/mapper/EmpMapper.xml"/>-->
<!--这两种方式使用其中一个-->
<!--2.Mapper代理方式,映射整个包下的所有文件-->
<package name="org.example.mapper"/>
</mappers>
三、MyBatis核心配置文件
需要严格按照mybatis官方给出的顺序对核心配置文件进行配置
https://mybatis.p2hp.com/configuration.html
四、配置文件完成增删改查
4.1、查询brand表,准备Brand实体类,创建BrandMapper接口,创建BrandMapper.xml
4.1.1查询所有数据
- 1.编写接口方法:findAllBrand();方法
- 参数:无
- 结果:List<Brand>
- 2.编写SQL语句:SQL映射文件
- 3.执行方法,测试类:BrandMapperTest
List<Brand> allBrand = mapper.findAllBrand();
解决表中字段名和实体类中属性名称不一致的情况:
1.sql映射文件中,起别名
<select id="findAllBrand" resultType="org.example.pojo.Brand">
select
id,brand_name as brandName,
company_name as companyName,
ordered,
description,status
from tb_brand
</select>
2.sql映射文件中,起别名:抽取字段 sql
<sql id="brand_column">
id,brand_name as brandName,company_name as companyName,ordered,description,status
</sql>
<select id="findAllBrand" resultType="org.example.pojo.Brand">
select
<!--引用sql片段-->
<include refid="brand_column"></include>
from tb_brand
</select>
3.sql映射文件中,定义resultMap标签:
<!--1.定义resultMap标签-->
<resultMap id="brandResultMap" type="org.example.pojo.Brand">
<result column="brand_name" property="brandName"></result>
<result column="company_name" property="companyName"></result>
</resultMap>
<!--2.在<select>查询标签中使用resultMap属性替换resultType属性-->
<select id="findAllBrand" resultMap="brandResultMap">
select * from tb_brand
</select>
4.在核心配置文件mybatis-config.xml中开启驼峰命名转换
<settings>
<!--是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--mapUnderscoreToCamelCase , true 表示开启-->
</settings>
4.1.2查看详情
查看某一条数据的详细信息
- 1.编写Mapper接口方法:findByID();
- 参数:id
- 结果(返回值):Brand
- 2.编写SQL语句:SQL映射文件
- 3.执行方法,测试
<select id="findByID" resultType="Brand" parameterType="int">
SELECT * FROM tb_brand where id = #{id}
<!--
* 参数占位符:
1.#{}:会将其替换为?,会自动添加单引号,防止sql注入
2.${}:会直接将其替换为字符串,直接拼接,不会添加单引号,存在sql注入
3.使用场景:
* 参数传递的时候:#{}
* 表名或者列名不固定的时候,模糊查询:${} ,存在sql注入
* parameterType:参数类型,可以省略
* 特殊字符处理:
1.转义字符
< 小于号: <
2.使用CDATA区
小于号: <![CDATA[ < ]]>
-->
</select>
4.1.3条件查询
- 多条件查询
1.编写接口方法:selectByCondition();
参数:所有查询条件
结果:List<Brand>
// 多条件查询,方法重载
// 1.散装参数:如果方法中有多个参数,需要使用@Param(“sql语句中使用的参数名”)注解指定参数名
List<Brand> selectByCondition(@Param("status") int status, @Param("companyName") String companyName, @Param("brandName") String brandName);
//2.对象参数,实体类参数:只需要保证sql中的参数名和实体类属性名对应上,即可设置成功
List<Brand> selectByCondition(Brand brand);
//3.使用map集合参数,只需要保证sql中的参数名和map集合的键的名称对应上,即可设置成功
List<Brand> selectByCondition(Map map);
2.编写SQL语句:SQL映射文件
<select id="selectByCondition" resultType="Brand">
select * from tb_brand where STATUS = #{status}
AND company_name LIKE #{companyName}
AND brand_name LIKE #{brandName};
</select>
3.执行方法,测试
@Test
public void testSelectByCondition() {
int status = 1;
String companyName = "华为";
String brandName = "华为";
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";
//1.散装参数
// List<Brand> brands = mapper.selectByCondition(status, companyName, brandName);
//2.实体类参数:
// Brand brand = new Brand();
// brand.setStatus(status);
// brand.setCompanyName(companyName);
// brand.setBrandName(brandName);
// List<Brand> brands = mapper.selectByCondition(brand);
//3.map集合
Map map = new HashMap();
map.put("status", status);
map.put("companyName", companyName);
map.put("brandName", brandName);
List<Brand> brands = mapper.selectByCondition(map);
brands.forEach(System.out::println);
}
存在bug:当条件不满足3个(即两个及以下)时,会出现错误,那么就需要用到动态SQL了。
- 多条件—动态条件查询,SQL语句会随着用户的输入或外部条件的变化而变化,成为动态SQL
<!-- 动态条件查询
使用if标签:条件判断 用于判断参数是否有值,使用test属性进行条件判断
test:逻辑表达式
问题:第一个条件不需要逻辑运算符and
解决:恒式过度一下 where 1=1
<where> 替换 where关键字
-->
<select id="selectByCondition" resultType="Brand">
select * from tb_brand
<where>
<if test="status != null">
and 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="org.example.pojo.Brand">
select * from tb_brand
where <!--可换成<where>标签,-->
<choose> <!--相当于switch-->
<when test="status != null"> <!--相当于case-->
status = #{status}
</when>
<when test="companyName != null && companyName != ''"> <!--相当于case-->
company_name LIKE '%${companyName}%'
</when>
<when test="brandName != null && brandName !=''"> <!--相当于case-->
brand_name LIKE '%${brandName}%'
</when>
<otherwise> <!--相当于default,当使用<where>标签时,可省略<otherwise>-->
1=1
</otherwise>
</choose>
</select>
4.2、添加
4.2.1添加
1.编写接口方法:void add(Brand brand);
- 参数:除了id之外的所有数据
- 返回值:void
2.编写SQL语句:SQL映射文件
<!--添加数据-->
<insert id="addBrand">
insert into tb_brand values(null,#{brandName},#{companyName},#{ordered},#{description},#{status});
</insert>
3.执行方法,测试
@Test
public void testAddBrand() {
Brand brand = new Brand(0, "小米", "小米", 1, "www.xiaomi.com", 100);
mapper.addBrand(brand);
// mybatis默认开启事务,需要手动提交事务
// 如果没有手动提交,mybatis默认回滚事务。
// 在获取sqlSession对象时,设置openSession(true),即自动提交事务
sqlSession.commit();
}
- MyBatis事务:
- openSession():默认开启事务,进行增删改操作后需要使用sqlSession.commit();手动提交事务
- openSession(true):可以设置为自动提交事务(关闭事务)
4.2.2添加 - 主键返回
在数据添加成功后,需要获取插入数据库数据的主键
比如:添加订单和订单项
1.添加订单
2.添加订单项,订单项中需要设置所属订单的id
<!--添加数据-->
<insert id="addBrand" useGeneratedKeys="true" keyProperty="id">
<!--添加主键返回 useGeneratedKeys="true" keyProperty="id"-->
insert into tb_brand values(null,
#{brandName},
#{companyName},
#{ordered},
#{description},
#{status});
</insert>
4.3、修改
4.3.1修改全部字段
1.编写接口方法:void update(Brand brand);
- 参数:所有数据
- 返回值:void
2.编写SQL语句:SQL映射文件
<!--修改-->
<update id="updateBrand">
update tb_brand
set
brand_name=#{brandName},
company_name=#{companyName},
ordered=#{ordered},
description=#{description},
status=#{status}
where
id=#{id};
</update>
3.执行方法,测试
/**
* 修改数据
*/
@Test
public void testUpdateBrand() {
int i = mapper.updateBrand(new Brand(2, "华为", "华为", 1, "www.huawei.com", 100));
// 返回受影响的行数
System.out.println(i);
}
4.3.2修改动态字段
1.编写接口方法:
- 参数:部分数据,封装到对象中
- 返回值:void
2.编写sql语句:sql映射文件
<update id="updateBrand">
update tb_brand
<set>
<if test="brandName != null and brandName != '' ">
brand_name=#{brandName},
</if>
<if test="companyName != null and companyName != '' ">
company_name=#{companyName},
</if>
<if test="ordered != null ">
ordered=#{ordered},
</if>
<if test="description != null and description != '' ">
description=#{description},
</if>
<if test="status != null ">
status=#{status}
</if>
</set>
where id=#{id};
</update>
3.执行方法,测试
@Test
public void testUpdateBrand() {
int id = 2;
String companyName = "华为技术有限公司";
Brand brand =new Brand();
brand.setId(id);
brand.setCompanyName(companyName);
int i = mapper.updateBrand(brand);
System.out.println(i);
}
4.4、删除
4.4.1删除一个
1.编写接口方法:void deleteById(int id);
- 参数:id
- 返回值:void
2.编写SQL语句:SQL映射文件
<!--删除一个-->
<delete id="deleteById">
delete from tb_brand where id=#{id};
</delete>
3.执行方法,测试
/**
* 删除单个数据
*/
@Test
public void testDeleteBrand() {
mapper.deleteById(6);
}
4.4.2批量删除
1.编写接口方法:deleteByIds(@Param(“ids”)int[] ids);
- 参数:ids数组
- 返回值:void
2.编写SQL语句:SQL映射文件
<!--批量删除-->
<delete id="deleteByIds">
delete from tb_brand where id in
<!--
mybatis会将数组参数,封装为一个Map集合,
默认数组名称:array
使用@Param注解改变map集合的默认key的名称,即array
没加@Param注解,就得使用array
collection:遍历数组
separator:分隔符
open:开始符号
close:结束符号-->
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
3.执行方法,测试
//删除多个数据
@Test
public void testDeleteByIds() {
int[] ids = {13, 12, 11};
mapper.deleteByIds(ids);
}
五、mybatis参数传递
MyBatis接口方法中可以接收各种各样的参数,MyBatis底层对于这些参数进行不同的封装处理方式
单个参数:
- 1.POJO类型:直接使用,属性名和参数占位符名称一致
- 2.Map集合:直接使用,键名和参数占位符名称一致
- 3.Collection:封装为map集合,可以使用@Param注解:替换Map集合中默认的arg键名
- map.put(“arg0”,collection集合)
- map.put(“collection”,collection集合)
- 4.List:封装为Map集合,可以使用@Param注解:替换Map集合中默认的arg键名
- map.put(“arg0”,list集合)
- map.put(“collection”,list集合)
- map.put(“list”,list集合)
- 5.Array:封装为map集合,可以使用@Param注解:替换Map集合中默认的arg键名
- map.put(“arg0”,数组)
- map.put(“array”,数组)
- 6.其他类型:直接使用
多个参数:封装为map集合,可以使用@Param注解:替换Map集合中默认的arg键名
map.put(“arg0”,参数值1)
map.put(“param1”,参数值1)
map.put(“param2”,参数值2)
map.put(“arg1”,参数值2)
MyBatis提供了ParamNameResolver类来进行参数封装
六、注解开发
使用注解开发会比配置文件开发更加方便
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}
查询:@Select
添加:@Insert
修改:@Update
删除:@Delete
提示:
1.注解完成简单功能
2.配置文件完成复杂功能
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
选择何种方式来配置映射,以及是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松地在基于注解和 XML 的语句映射方式间自由移植和切换。