Mapper代理开发
概述
之前我们写的代码是基本使用方式,它也存在硬编码的问题,如下:
这里调用
selectList()
方法传递的参数是映射配置文件中的 namespace.id值。这样写也不便于后期的维护。如果使用 Mapper 代理方式(如下图)则不存在硬编码问题。通过上面的描述可以看出 Mapper 代理方式的目的:
- 解决原生方式中的硬编码
- 简化后期执行SQL
使用Mapper代理要求
- 接口名和映射配置文件名保持一致
- 接口和配置文件必须在同一个包中
- namespace和接口的全类名保持一致
-
sql的id的值要和接口中的方法名保持一致 返回值也要一致
目录结构如下
image.png
resources目录创建跟Java一样的目录结构,就可以使接口和配置文件在同意目录下,如下就是执行完成后的dao文件目录中的效果
image.png
可以看到这俩个的确在一个文件夹内
设置SQL映射文件的namespace属性为Mapper接口全限定名
image.png
在Mapper接口中定义方法,方法名就是SQL映射文件中的sql语句的id,并保持参数类型和返回值类型一致
image.png
案例代码实现
-
在
com.itheima.mapper
包下创建 UserMapper接口,代码如下:public interface UserMapper { List<User> selectAll(); User selectById(int id); }
-
在
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 测试类,代码如下:/** * Mybatis 代理开发 */ 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. 执行sql //3.1 获取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>
还有上一篇讲过核心配置文件可以配置多个环境,id是每个环境的名字,在environment中使用default='环境id'来各更换环境,一般配一个就可以
# mybatis练习
## 一、环境准备
* 数据库表(tb_brand)及数据准备
```sql
-- 删除tb_brand表
drop table if exists tb_brand;
-- 创建tb_brand表
create table tb_brand
(
-- id 主键
id int primary key auto_increment,
-- 品牌名称
brand_name varchar(20),
-- 企业名称
company_name varchar(20),
-- 排序字段
ordered int,
-- 描述信息
description varchar(100),
-- 状态:0:禁用 1:启用
status int
);
-- 添加数据
insert into tb_brand (brand_name, company_name, ordered, description, status)
values ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),
('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界', 1),
('小米', '小米科技有限公司', 50, 'are you ok', 1);
-
实体类 Brand
在
com.itheima.pojo
包下创建 Brand 实体类。public class Brand { // id 主键 private Integer id; // 品牌名称 private String brandName; // 企业名称 private String companyName; // 排序字段 private Integer ordered; // 描述信息 private String description; // 状态:0:禁用 1:启用 private Integer status; //省略 setter and getter。自己写时要补全这部分代码 }
在 com.itheima.mapper
包写创建名为 BrandMapper
的接口
public interface BrandMapper{
// 这里面写方法
}
在resources资源目录下创建com.itheima.mapper
目录,创建BrandMapper.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"><!-- 环境选择 因为普遍会一个环境 所以和下面id写的一样即可-->
<environment id="development"><!-- 这里是每个环境的id标识,当有多个环境可以在default中改变id值来切换环境-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false"/> <!--这里更改自己的数据库 -->
<property name="username" value="root"/> <!--这里更改自己的数据库账号 -->
<property name="password" value="root"/> <!--这里更改自己的数据库密码 -->
</dataSource>
</environment>
</environments>
<mappers>
<!--加载sql映射文件-->
<!-- <mapper resource="UserMapper.xml"/>
<mapper resource="StudentMapper.xml"/>-->
<package name = "com/itheima/dao" /> <!--扫描包下的所有配置文件,这样连接多个配置文件可以省点心 -->
</mappers>
</configuration>
二、查询
根据id查询
接口中的方法
/**
* 查询单独数据
* @param id
* @return
*/
Brand selectId(int id);
配置文件中的sql语句(namespace的值是接口的全类名,这样借口才会跟配置文件连接到一起)
<select id="selectId" resultType="brand">
select * from tb_brand where id = #{id};
</select>
测试代码
@Test
public void SelectIdTest() throws IOException {
//获取SqlSessionFactory
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 获取SqlSesion对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取Mapper接口的代理对象
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
Brand brand = mapper.selectId(4);
System.out.println(brand);
}
参数占位符
查询到的结果很好理解就是id为1的这行数据。而这里我们需要看控制台显示的SQL语句,能看到使用?进行占位。说明我们在映射配置文件中的写的 #{id}
最终会被?进行占位。接下来我们就聊聊映射配置文件中的参数占位符。
mybatis提供了两种参数占位符:
#{} :执行SQL时,会将 #{} 占位符替换为?,将来自动设置参数值。从上述例子可以看出使用#{} 底层使用的是 `PreparedStatement`
${} :拼接SQL。底层使用的是 `Statement`,会存在SQL注入问题。如下图将 映射配置文件中的 #{} 替换成 ${} 来看效果
多条件查询(模糊查询)
接口中定义的方法
// List<Brand> SelectName(String brandName,String companyName,int status);//这种不起别名 配置文件里就需要使用arg0 arg1 arg2来进行匹配 代码可读性差
List<Brand> SelectName( @Param("brandName")String brandName,
@Param("companyName")String companyNmae,
@Param("status")int status);// 这样起一个别名就可以自定义名字
配置文件的sql语句
<!-- <select id="SelectName" resultType="brand">
select * from tb_brand
where
brand_name = #{arg0}
and
company_name = #{arg1}
and
status = #{arg2}
</select>-->
<select id="SelectName" resultType="brand">
select * from tb_brand
where
brand_name like concat('%',#{brandName},'%')
and
company_name like concat('%',#{companyName},'%')
and
status like concat('%',#{status},'%')
</select>
测试代码
@Test
public void SelectNameTest() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
List<Brand> brands = mapper.SelectName("麻辣", "麻辣", 1);
for (Brand brand : brands) {
System.out.println(brand);
}
}
但是当用户输入的时候其他参数不传,只传一个参数就会查不出数据
更改后的sql语句
<select id="SelectName" resultType="brand">
select * from tb_brand
<where>
<if test="brandName != null">
and brand_name like concat('%',#{brandName},'%')
</if>
<if test="companyName != null">
and company_name like concat('%',#{companyName},'%')
</if>
<if test="status != null">
and status like concat('%',#{status},'%')
</if>
</where>
</select>
if标签判断是否为空,为空就不拼接这段代码,不过也会出现问题,当第一个为空时,后面的and就会拼上去,这时候where标签就起了作用,会将多与and去掉 为了方便,我就直接所有的都加了and,concat关键字时拼接,拼接条条件使其sql语句完整,下面就是测试代码输出的信息,concat将其模糊查询的条件拼接起来
[DEBUG] 22:08:20.041 [main] c.i.d.B.SelectName - ==> Preparing: select * from tb_brand WHERE brand_name like concat('%',?,'%') and company_name like concat('%',?,'%') and status like concat('%',?,'%')
[DEBUG] 22:08:20.080 [main] c.i.d.B.SelectName - ==> Parameters: (String), 麻辣(String), 1(Integer)
[DEBUG] 22:08:20.101 [main] c.i.d.B.SelectName - <== Total: 5
Brand(id=5, brandName=麻辣王子, companyName=麻辣王子湖南有限公司, ordered=200, description=不辣你来打我, status=1)
Brand(id=6, brandName=麻辣王子, companyName=麻辣王子湖南有限公司, ordered=200, description=不辣你来打我, status=1)
Brand(id=7, brandName=麻辣王子, companyName=麻辣王子湖南有限公司, ordered=200, description=不辣你来打我, status=1)
Brand(id=8, brandName=麻辣王子, companyName=麻辣王子湖南有限公司, ordered=200, description=不辣你来打我, status=1)
Brand(id=9, brandName=麻辣王子, companyName=麻辣王子湖南有限公司, ordered=200, description=不辣你来打我, status=1)
测试代码
@Test
public void SelectNameTest() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
List<Brand> brands = mapper.SelectName("", "麻辣", 1);
for (Brand brand : brands) {
System.out.println(brand);
}
}
单个条件查询(动态查询)
当查询的时候,给用户三个选项让用户选择,然而我们并不知道用户要使用哪一个条件来查询.
这种需求需要使用到 choose(when,otherwise)标签
实现, 而 choose
标签类似于Java 中的switch语句。
接口中定义的方法
List<Brand> SelectOne(Brand brand);
配置文件的sql语句
<select id="SelectOne" resultType="brand">
select * from tb_brand
<where>
<choose>
<when test="brandName != null">
brand_name like concat('%',#{brandName},'%')
</when>
<when test="brandName != null">
company_name like concat('%',#{companyName},'%')
</when>
<when test="brandName != null">
status like concat('%',#{status},'%')
</when>
<otherwise>
brand_name = null;
</otherwise>
</choose>
</where>
</select>
测试代码
@Test
public void SelectOneTest() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
List<Brand> brands = mapper.SelectOne(new Brand(0,"麻辣王子",null,null,null,0));
for (Brand brand : brands) {
System.out.println(brand);
}
//当 brandName companyName status 这三个参数都不传值,默认 brandName = null 一个值都查不出来
}
三、增加
接口中定义的方法
int insertData(Brand brand);
配置文件的sql语句
<insert id="insertData">
insert into tb_brand values(#{id},
#{brandName},
#{companyName},
#{ordered},
#{description},
#{status});
</insert>
测试代码
@Test
public void InsertDataTest() throws IOException {
//获取SqlSessionFactory
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 获取SqlSesion对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取Mapper接口的代理对象
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
Brand brand = new Brand(6,"麻辣王子","麻辣王子湖南有限公司",200,"不辣你来打我",1);
int rows = mapper.insertData(brand);
System.out.println("成功添加了"+rows +"条数据");
sqlSession.commit();//mybatis 中 默认开启事务 需要提交事务
}
四、修改
接口中定义的方法
int updateData(Brand brand);
配置文件的sql语句
<update id="updateData">
update tb_brand set
brand_name = #{brandName},
company_name = #{companyName},
ordered = #{ordered},
description = #{description},
status = #{status} where id=#{id};
</update>
测试代码
@Test
public void updataDataTest() throws IOException {
//获取SqlSessionFactory
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 获取SqlSesion对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取Mapper接口的代理对象
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
int rows = mapper.updateData(new Brand(5,"麻辣王子","麻辣王子湖南有限公司",200,"不辣你来打我",1));
System.out.println("成功修改了"+rows +"条数据");
sqlSession.commit();
}
五、删除
删除一条数据
接口中定义的方法
int deleteData(int id);
配置文件的sql语句
<delete id="deleteData">
delete from tb_brand where id = #{id};
</delete>
测试代码
@Test
public void deleteDataTest() throws IOException {
//获取SqlSessionFactory
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 获取SqlSesion对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取Mapper接口的代理对象
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
int rows = mapper.deleteData(6);
System.out.println("成功删除了"+rows +"条数据");
}
批量删除
接口中的方法
//批量删除
int deleteBatch(int []ids);
配置文件的sql语句
<delete id="deleteBatch">
delete from tb_brand where
id in
<foreach collection="array" separator="," item="id" open="(" close=")">
#{id}
</foreach>
</delete>
foreach 标签
用来迭代任何可迭代的对象(如数组,集合)。
- collection 属性:
- mybatis会将数组参数,封装为一个Map集合。
- 默认:array = 数组
- 使用@Param注解改变map集合的默认key的名称
- mybatis会将数组参数,封装为一个Map集合。
- item 属性:本次迭代获取到的元素。
- separator 属性:集合项迭代之间的分隔符。
foreach
标签不会错误地添加多余的分隔符。也就是最后一次迭代不会加分隔符。 - open 属性:该属性值是在拼接SQL语句之前拼接的语句,只会拼接一次
- close 属性:该属性值是在拼接SQL语句拼接后拼接的语句,只会拼接一次
测试代码
@Test
public void deleteBatch() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
int i = mapper.deleteBatch(new int[]{7,8});
System.out.println("删除了"+i +"条数据");
sqlSession.commit();
}
注意 增删改 都需要提交事务,不然数据无法更新,只有查询不要提交事务