02JavaWeb之MyBatis详解、SqlSession执行、mapper代理执行、动态SQL语句、注解开发、resultMap、resultType、多参数传递

MyBatis

  • MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发,它封装了JDBC大部分的操作。
  • MyBatis 本是 Apache 的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
  • 官网:https://mybatis.org/mybatis-3/zh/index.html

Mybatis就是对jdbc代码进行了封装。

首先新建maven项目,在pom.xml中添加依赖,其中mysql-connector-javamysql驱动,mybatis需要用到,如果是用数据库连接池Druid则不用,因为其内置了mysql驱动。

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.46</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.10</version>
    </dependency>
</dependencies>
配置文件开发

文件结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2OvjElE0-1661335163347)(assets/image-20220824170421795.png)]

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;
}

有两个文件一个是BrandMapper.java(接口文件),另一个是BrandMapper.xml(Sql配置文件,可以在这里写sql语句)

// BrandMapper.java
public interface BrandMapper {
    //查询所有
    List<Brand> selectAll();
}
<!-- BrandMapper.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"> // 这里的namespace可以随意
    <select id="selectAll" resultType="org.mybatis.pojo.Brand">
        select * from tb_brand; // 在这里写sql语句,结果类型要写全。
    </select>
</mapper>

然后最重要的就是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>
    <environments default="development">
        <!-- 配置多环境,默认为development-->
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!--链接本地数据库-->
                <property name="url" value="jdbc:mysql:///db_test?useSSL=false&amp;useServerPrepStmts=true"/>
                <property name="username" value="root"/>
                <property name="password" value="xxxx"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!-- 单独加载sql映射文件-->
        <mapper resource="BrandMapper.xml"/>
   
    </mappers>
</configuration>

接下来就可以执行sql语句了

public class MybatisDemo {
    public static void main(String[] args) throws IOException {
        // 加载配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 由工厂构建器构建一个SqlSessionFactory工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //2. 获取SqlSession对象,用它来执行sql
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //3. 执行sql
        List<Brand> brands = sqlSession.selectList("test.selectAll");
        for(Brand b:brands){
            System.out.println(b);
        }
    }
}

这里用的是SqlSession来执行的

SqlSession sqlSession = sqlSessionFactory.openSession();
List<Brand> brands = sqlSession.selectList("test.selectAll");

参数是一个字符串,该字符串必须是映射配置文件的namespace.id。 此时的namespace可以随便起名,因为这里是准确传入namespace的,java能够找到。

mapper代理开发

基于上面的条件,第二种执行方式(推荐)

public class MybatisDemo {
    public static void main(String[] args) throws IOException {
        // 加载配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 由工厂构建器构建一个SqlSessionFactory工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //2. 获取SqlSession对象,用它来执行sql
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // sqlSession执行
//        List<Brand> brands = sqlSession.selectList("test.selectAll");
        // mapper代理开发
        BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
        List<Brand> brands = mapper.selectAll();
        for(Brand b:brands){
            System.out.println(b);
        }
    }
}

关键在这两句

BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);//把mapper类传入
List<Brand> brands = brandMapper.selectAll();//然后就可以直接调用方法

但是这种方式要求比较高:需要

  • 定义与SQL映射文件同名的Mapper接口并且将Mapper接口和SQL映射文件放置在同一目录下

  • 设置SQL映射文件的namespace属性为Mapper接口全限定名

  • 在 Mapper 接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致

因此我们需要对项目做出相应的修改,针对第一条:在resource下新建目录: com/example/mapper

BrandMapper.xml文件放到该目录下,并将BrandMapper.xmlnamespace修改为BrandMapper接口的全限定名。

<mapper namespace="com.example.mapper.BrandMapper">
    <select id="selectAll" resultType="com.example.pojo.Brand">
        select * from tb_brand
    </select>
</mapper>

另外,因为BrandMapper.xml的位置发生了改变,还需要对mybatis-config.xml进行修改:

<mappers>
    <mapper resource="com/example/mapper/BrandMapper.xml"/>
</mappers>

注意这里的路径分隔符是 / 而不是.,因为他是处于resources下的,不是package,而是目录。

经过上述修改之后,文件结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q3Un3zfO-1661335163348)(assets/image-20220824172840626.png)]

可以看到,在target目录下,BrandMapper接口和BrandMapper.xml文件在同一个目录下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3dGp52km-1661335163349)(assets/image-20220719211135635.png)]

然后就可以通过mapper代理的方式执行sql语句了

BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);//把mapper类传入
List<Brand> brands = brandMapper.selectAll();//然后就可以直接调用方法

小结

  • 首先要保证Sql映射文件(BrandMapper.xml)和接口(BrandMapper.java)是同名的,且在同一个目录下。
    • 例如接口位置: com.example.mapper.BrandMapper
    • 映射文件位置:com/example/mapper/BrandMapper.xml
  • 其次就是接口映射文件中的namespace必须是接口的全限定名com.example.mapper.BrandMapper
    • 之前通过session执行sql的时候是用的namespace.id的方式,这种方式namespace可以随便写都能找到。
    • mapper代理开发的方式寻找sql执行方法的时候默认的namespace是接口全限定名。
    • 因此我们要把namespace写成接口全限定名mybatis才能找到执行sql的方法在哪个接口。
  • 注意要在mybatis-config.xml中导入mapper映射文件com/example/mapper/BrandMapper.xml
    • 可以使用包扫描的方法,这样就不用挨个导入了。
    • 这里是目录,分隔符为/
mapper代理开发改进
resultType别名

表示org.mybatis.pojo下的所有实体类都可以起别名。

<typeAliases>
    <package name="com.example.pojo"/>
</typeAliases>

而这个包下放的是Brand类。因此这个包下的类都可以使用别名。

这样起别名的好处是在接口映射文件的resultType中可以使用简单的名称:resultType="Brand",而不用写全名。

<mapper namespace="com.example.mapper.BrandMapper">
    <select id="selectAll" resultType="Brand">
        select * from tb_brand
    </select>
</mapper>
包扫描加载sql映射文件

如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载。也就是将核心配置文件的加载映射配置文件的配置修改为:

    <mappers>
<!--        <mapper resource="com/example/mapper/BrandMapper.xml"/>-->
        <package name="com/example/mapper"/>
    </mappers>
resultMap

这个是用来解决实体类的属性名和数据库中列名不一致的问题的。

例如实体类中喜欢用驼峰: brandCompany,而数据库中喜欢用下划线: brand_company,这样会导致查询的结果无法封装到实体类中去,或者实体类中的数据无法更新到数据库中去。

Brand{id=1, brandName='null', companyName='null', ordered=50, description='好吃不上火 ', status=1}

在 BrandMapper.xml中:

<mapper namespace="com.example.mapper.BrandMapper">
    <resultMap id="brandResult" type="brand">
        <result property="companyName" column="company_name"/>
        <result property="brandName" column="brand_name"/>
    </resultMap>
    <select id="selectAll" resultMap="brandResult">
        select * from tb_brand
    </select>
</mapper>
mybatis传参数
没有参数的情况
List<Brand> selectAll();
<select id="selectAll" resultType="Brand">
    select * from tb_brand
</select>
只有一个参数的情况
Brand selectById(int id); //接口中的定义

映射文件中的定义

<select id="selectById" resultType="Brand">
    select * from tb_brand where id = #{id};
</select>

使用:

BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
Brand brand = brandMapper.selectById(1);
多个参数的情况

方法一:通过构造一个POJO对象传入多个参数

Brand selectByIdAndBrandNameByObject(Brand brand);
<!--     用Brand对象最为参数来传递多参数,要求{}的名字和Brand对象的属性名相同-->
<select id="selectByIdAndBrandNameByObject" resultMap="brandResult">
    select * from tb_brand where id = #{id} and brand_name = #{brandName};
</select>
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
Brand b = new Brand();
b.setId(10);
b.setBrandName("中国好人");
Brand brand = brandMapper.selectByIdAndBrandNameByObject(b);

方法二:通过注解传参数

Brand selectByIdAndBrandNameByAnnotation(@Param("ID") int ID, @Param("bName") String bName);
<!--    通过注解的方式来传递多参数,要求{}的名字就是注解内的名字paramName  @Param(paramName)-->
<select id="selectByIdAndBrandNameByAnnotation" resultMap="brandResult">
    select * from tb_brand where id = #{ID} and brand_name = #{bName};
</select>
Brand brand = brandMapper.selectByIdAndBrandNameByAnnotation(10,"中国好人");

方法三:通过传入一个map传参数

Brand selectByIdAndBrandNameByMap(Map map);
<!--    通过传入一个map的方式来传递多参数,要求{}的名字是map的key-->
<select id="selectByIdAndBrandNameByMap" resultMap="brandResult">
    select * from tb_brand where id = #{key_id} and brand_name = #{key_brandName};
</select>
Map map = new HashMap<String,String>();
map.put("key_id",10);
map.put("key_brandName","中国好人");
Brand brand = brandMapper.selectByIdAndBrandNameByMap(map);

以上三种方式,最不推荐第三种,如果参数较少(两三个)那么用注解比较方便;如果参数多达四五个,那么构造一个POJO对象比较方便。

mybatis传递参数的原理

mybatis传递参数,可以传递单个参数或者多个参数,而单个参数可以是:

  • POJO对象,例如传入一个Brand b;
  • Map集合类型
  • Collection集合类型(Map和Collection是两个派系)
  • List集合类型
  • Array类型
  • 其他类型(例如int String 之类的)

而对于多个参数传递,mybatis底层对于多个参数的处理是将这些参数封装成Map集合对象,值就是参数值。而键在没有使用@Param注解的时候命名为arg0,arg1…或者是 param1,param2…以此类推。

我们作以下实验:

Brand selectByIdAndStatus(int id, int status); //接口定义,没有使用注解,也没有使用map传入多个参数
<select id="selectByIdAndStatus"  resultMap="brandResult">
    select * from tb_brand where id = #{arg0} and status = #{arg1};
</select>

这里的参数使用arg0, arg1表示两个参数,能够返回正确的结果:

Preparing: select * from tb_brand where id = ? and status = ?; //arg0 arg1被替换成了占位符
Parameters: 1(Integer), 1(Integer)
Brand{id=1, brandName='三只松鼠', companyName='三只松鼠股份有限公司', ordered=5, description='好吃不上火', status=1} //查询结果封装成的Brand对象

我们进行更改同样能够得到上述的结果:

<select id="selectByIdAndStatus" resultMap="brandResult">
    select * from tb_brand where id = #{param1} and status = #{param2}; //参数替换为param1,param2...以此类推
</select>

实际上就算替换成param1,arg1这样的组合也是可以的。即arg0和param1对应的是同一个参数。以此类推。

在接口方法参数上使用@Param注解,mybatis会将arg开头的键名替换为对应注解的属性值。

注意:只是arg参数进行了替换,param参数没有替换!

进行如下更改

Brand selectByIdAndStatus(@Param("id") int id, @Param("status") int status); //接口参数使用注解

发现param参数还能继续使用不报错,而如果替换成arg0,arg1就会报错,因为在map中这两个key已经被替换成了id和status.

<select id="selectByIdAndStatus" resultMap="brandResult">
    select * from tb_brand where id = #{param1} and status = #{param2}; //参数替换为param1,param2...以此类推
</select>
查询结果名称处理

由于数据库中的名称和POJO对象的属性名不同:例如数据库中为company_name,POJO对象为companyName

这样导致查询结果无法对应,那么mybatis就不能自动封装为POJO对象。

方法一:查询的时候使用别名,即在sql语句中使用as

select company_name as companyName, ...

这样写比较麻烦

方法二:在映射文件中使用sql片段

也就是把sql语句中 替换的这部分提取出来,以后的查询语句可以复用。这样不方便,因为不是所有字段都用得上。

方法三:使用resultMap

<resultMap id="brandResult" type="brand">
    <result column="brand_name" property="brandName"/>
    <result column="company_name" property="companyName"/>
</resultMap>

column是数据库中的列标签,property是给定的属性,即别名。

<select id="selectById" resultType="Brand">
    select * from tb_brand where id = #{id};
</select>

将resultType替换成resultMap即可

<select id="selectById" resultMap="brandResult">
    select * from tb_brand where id = #{id};
</select>

最后,如果是使用resultType的话,需要写全名,即POJO类所在包名+类名

例如 resultType = org.mybatis.pojo.Brand

但是如果在核心配置文件中加上这个片段

<typeAliases>
    <package name="org.mybatis.pojo"/>
</typeAliases>

则表明这个包下的类名都可以使用别名,且不区分大小写。resultType = Brand或者resultType = brand都是可以的。

resultType适用于查询结果中没有名称不一致的问题的情况。

另外在核心配置文件中需要导入映射文件

<mappers>
<!--        <mapper resource="org/mybatis/mapper/BrandMapper.xml"/>-->
    <package name="org/mybatis/mapper"/>
</mappers>

使用<package name="org/mybatis/mapper"/>可以一次将这个目录下的所有映射文件导入。

最后mybatis的核心配置文件是有顺序的,例如typeAliases就必须在environments的前面,否则无法生效。

动态SQL语句
if 和 where标签

多条件组合。

<select id="selectByCondition" resultMap="brandResult">
    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>
  • 不用再写where,标签会自动加上
  • 每一句都要写and,mybatis会自动把第一个and 去掉。

mapper接口如下

List<Brand> selectByCondition(Brand brand);

使用过程如下:此时传入的参数为一个POJO对象是最好的

Brand b = new Brand();
b.setStatus(1);
List<Brand> brands = brandMapper.selectByCondition(b);

打印mybatis执行日志可以看到实际执行sql语句为:(使用了预编译,即?作为占位符然后传入参数的方法,而不是拼接字符串的方法)

 Preparing: select * from tb_brand WHERE status = ?
Parameters: 1(Integer)

如果更改为

Brand b = new Brand();
b.setBrandName("三只松鼠");
List<Brand> brands = brandMapper.selectByCondition(b);
Preparing: select * from tb_brand WHERE brand_name like ?
Parameters: 三只松鼠(String)

可见where和if的组合能够动态选择条件。

choose和when标签

单个条件选择

  • 不用写where,标签会自动加上
  • 不用写and,因为这是单个条件选择,只能多选一,而不是条件的组合。
<select id="selectByConditionSingle" resultMap="brandResult">
    select * from tb_brand
    <where>
        <choose>
            <when test="status!=null">
                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>
Brand selectByConditionSingle(Brand brand);
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
Brand b = new Brand();
b.setBrandName("三只松鼠");
Brand brand = brandMapper.selectByConditionSingle(b);
Preparing: select * from tb_brand WHERE brand_name like ?
Parameters: 三只松鼠(String)
主键返回

多用于插入一条记录之后,返回主键。这里主键是自动增长的。(此处存疑,我理解的主键返回是:主键自增,插入数据之后返回主键的值,但是经过实验,好像都是插入成功返回1)

<insert id="add" useGeneratedKeys="true" keyColumn="id">
    insert into tb_brand(brand_name, company_name, ordered, description, status) VALUES
    (#{brandName},#{companyName},#{ordered},#{description},#{status} );
</insert>
Brand b = new Brand();
b.setBrandName("中国好人aaa");
b.setCompanyName("中国好人有限公司");
b.setStatus(1);
b.setOrdered(99);
b.setDescription("中国好人,做中国好产品!");
brandMapper.add(b);
sqlSession.commit();

尤其注意:需要手动提交事务,否则数据没有真正插入到数据库中

另外:id自增不一定是连续的。用户如果设置了已经存在的主键也不会影响插入,mysql会自动赋值一个主键。

或者可以设置自动提交事务

SqlSession sqlSession = sqlSessionFactory.openSession(true); 
//设置自动提交事务,这种情况不需要手动提交事务了
update和set
 <update id="update">
        update tb_brand
        <set>
            <if test="status != null">
                 status = #{status},
            </if>
            <if test="companyName!=null and companyName != '' ">
                 company_name =  #{companyName},
            </if>
            <if test="brandName!=null and brandName != '' ">
                 brand_name = #{brandName},
            </if>
            <if test="description != null and description != '' ">
                description = #{description},
            </if>
            <if test="ordered != null">
                ordered = #{ordered}
            </if>
        </set>
        where id = #{id};
    </update>
  • 不用写set,标签会自动加上
  • 除了最后一个if之外,其余的都要加上逗号, 因为set语句就是分开设置的,而不是用and连接的。
update `tb_brand` set brand_name="新产品",ordered=-1 WHERE id = 24;
批量删除 delete foreach
<delete id="deleteById">
    delete from tb_brand where id in
    <foreach collection="array" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
    ;
</delete>
  • array是参数默认的名字,如果使用注解传参,则会更改参数名字,此处也要做对应的修改
void deleteById(int[] ids);
int[] ids = {23,24,25};
brandMapper.deleteById(ids);
sqlSession.commit();//如果没有设置自动提交事务,涉及到对数据库的修改需要手动提交事务。
Preparing: delete from tb_brand where id in ( ? , ? , ? ) ;
Parameters: 23(Integer), 24(Integer), 25(Integer)

设置自动提交事务: SqlSession sqlSession = sqlSessionFactory.openSession(true);

注解实现CURD

使用注解开发会比配置文件开发更加方便,但是仅仅适用于sql语句比较简单的情况,例如

@Select(value="select * from tb_brand where id = #{id}")
Brand select(int id);//mapper接口

此外还有

  • @Insert
  • @Update
  • @Delete
@Select("select * from tb_brand where id = #{id}")
List<Brand> selectByIdByAnnotation(int id);

使用注解就实现了sql查询,这样就不用在映射文件中写sql语句了。

final List<Brand> brands = brandMapper.selectByIdByAnnotation(1);
System.out.println(brands);


brandMapper.deleteById(ids);
sqlSession.commit();//如果没有设置自动提交事务,涉及到对数据库的修改需要手动提交事务。

设置自动提交事务: SqlSession sqlSession = sqlSessionFactory.openSession(true);

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值