2.MyBatis

106 阅读10分钟

MyBatis

图片.png

JDBC的缺点

注册驱动和SQL语句,后续可能会发生变化,带来繁琐的维护操作。

图片.png

MyBatis的改进

图片.png

MyBatis快速入门

图片.png

1.创建user表

图片.png

图片.png

2.创建模块导入坐标

要使用 MyBatis, 只需将 [mybatis-x.x.x.jar]文件置于类路径(classpath)中即可。

如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.5</version>
</dependency>

再导入mysql测试

 <!--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>

导入logback日志,并且还需要他的配置文件loback.xml

<!-- 添加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>

3.编写MyBatis核心配置文件

用来替换数据库的连接信息

从 XML 中构建 SqlSessionFactory

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

新建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">
    <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>
  //加载sql映射文件
  <mappers>
    <mapper resource="UserMapper.xml"/>
  </mappers>
</configuration>

注:UserMapper.xml和mybatis-config.xml是在resources文件夹下的,是平级的,所以可以直接写路径

4.编写SQL映射文件

UserMapper.xml;操作User表的
OrderMapper.xml;操作Order表的

<?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">
  //namespace:名称空间
<mapper namespace="test">
//id是sql语句的唯一标识,resultType是返回结果的类型
  <select id="UserSelectAll" resultType="com.itheima.pojo.User">
    select * from User;
  </select>
  <update/>
  <delete/>
  ......
</mapper>

创建对应的类

com.itheima.pojo.User

public class User{
    //表里有什么属性,我就写什么属性
    private String id;
    private String username;
    private String passowrd;
    private String gender;
    private String addr;
    ......后面再写setter和getter方法以及toString
}

测试类

public static void main(String[] args){
    //1.加载mybatis的核心配置文件,获取SqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourcesAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    
    //2.获取SqlSession对象,用它来执行sql
    SqlSession sqlSession = sqlSessionFactory.openSession();
    
    //3.执行sql
    //里面传入sql的[名称空间.id(唯一标识)]
    List<Object> users = sqlSession.selectList("test.UserSelectAll");
    //4.释放资源
    sqlSession.close();
}

补充

图片.png

详细步骤

图片.png

  1. Driver那里的jar包,别忘记导入

图片.png

  1. 就和我们的mysql一样了,也可以书写sql语句

图片.png

图片.png

Mapper代理开发

图片.png

前面我们这样写,是通过sqlSession中原始的select方法实现的,并且后面的类型也是我们写死的。相当于是一个硬编码,后续维护依然不方便。

图片.png

Mapper使用原则

图片.png

  1. UserMapper.xml配置,我想用Mapper代理开发,也需要写一个和他同名的Mapper接口:UserMapper.java

  2. 让接口和配置文件在同一个包下; 在resources中新建一个Directory文件夹:不能使用. 来作为分割,而是使用/ 。保证包名相同即可,即使不在一个目录

图片.png

图片.png

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

图片.png

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

图片.png

  1. 修改曾经的mybatis-config.xml路径

图片.png

  1. 修改测试类; 获取的是UserMapper接口类型 图片.png

  2. 如果Mapper接口名称和SQL映射文件名称相同,并且再同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载

图片.png

MyBatis核心配置文件

别忘了要遵守标签的前后顺序

图片.png

图片.png

environments是环境,配置是开发/测试环境,并且可以用来连接 transactionManager不用管,用来:事物管理的 dataSource:不用管,配置用户名密码就行 (放在environment上面)类型别名typeAlias: 我们通过Mapper.xml配置时,返回类型都需要把包名也上,很麻烦,但是我们可以通过配置别名来改进。

图片.png

不仅可以不用写包名,而且也不区分大小写了。

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

MyBatis实例

图片.png

  1. 数据库表tb_brand

图片.png

  1. 实体类Brand

图片.png

  1. 测试用例 测试用例存储在/test/java/com.itheima.test.test.MyBatisTtest

  2. 安装MyBatisX插件

帮助我们在映射配置文件中和映射中的方法对应起来,让我们查找对应sql语句的时候,不会那么麻烦。

图片.png

点击小鸟,就可以帮你跳转,如果你只在映射中书写,映射配置文件没写,就会报如下的波浪线,点一下Alt+Enter,就会帮你在配置文件中自动生成。

图片.png

补充

编写Mybatis的一般步骤:

1.编写接口方法:Mapper接口 2.编写SQL语句:SQL映射文件(Mapper.xml) 3.执行方法,测试

图片.png

查询

查询所有数据

查询出来的结果,一定是一个Brand类型的一个集合。

  1. 编写Mapper接口方法 BrandMapper
public interface BrandMapper{
    public List<Brand> selectAll();
}
  1. 编写SQL映射文件 名字要和接口一样:BrandMapper.xml
//命名空间,返回类型可以直接写路径,不用写详细路径
<mapper namespace = "com.itheima.mapper.BrandMapper">
    //编写sql语句resultType = "com.itheima.pojo.Brand"
    <select id = "selectAll" resultType = "Brand">
        select * from tb_brand;
    </select>
</mapper>
  1. 书写测试用例 test/java/com.itheima.test/MyBatisTest
@Test
public void testSelectAll() thorws IOException{
    //1.获取SqlSessionFactory(固定不变)
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    
    //2.获取sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    
    //3.获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class)
    
    //4.执行方法,将查询出来的结果保存在brands集合中
    List<Brand> brands = brandMapper.selectAll();
    System.out.println(brands);
    
    //5.释放资源
    sqlSession.close();
}

补充

其实这样写是会存在问题的,因为数据库中,我们的字段是brand_name;而Brand实体类中,我们是randName这种驼峰命名,不能匹配上,就不能帮我们封装数据。

在BrandMapper.xml中配置修改

第一种方法:起别名,对brand_name设置别名为实体类中的名字,as可以省略

<select id = "selectAll" resultType = "brand">
    select id,brand_name as brandName...
</select>

图片.png

第二种方法:sql片段

<sql id = "brand_column'>
    将刚刚的起别名放进来
    id,brand_name as brandName...
</sql>
后面就可以引用sql片段
<select id = "selectAll" resultType="brand">
    select
        <include refid = "brand_column"/>
    from tb_brand;
</select>

图片.png

第三种方法:resultMap【最推荐】
1.定义<resultMap>标签
2.在<select>标签中使用resultMap属性替换resultType属性

//先定义一个resultMap标签,和brand类型映射,共有id和result两个标签。
//id是主键字段映射,result是一般字段的映射

<resultMap id = "brandResultMap" type = "brand">

//因为id主键字段是相同的,一般字段不同,所以只写result
//前面是数据库中的字段名,后面的实体类中的名称
    <result column="brand_name" property = "brandName"/>
    <result column="company_name" property = "companyName"/>
</resultMap>

最后再将 resultType = "brand"改为resultMap="刚刚定义的id名"
<select id = "selectAll" resultMap="brandResultMap">
    select * from tb_brand;
</select>

查询详情

根据id,查询出brand类型的集合对象

图片.png

  1. 编写Mapper接口方法
Brand selectById(int id);
  1. 编写SQL映射文件

#{..}和接口方法中的参数保持一致
#{}$ {} 都表示:参数占位符
#{} 会将其替换为 防止SQL注入;
${} 会直接拼接sql,会存在SQL注入问题;

<select id = "selectById" resultMap="brandResultMap">
    select * 
    from tb_brand where id = #{id};
    
</select>
  1. 编写测试方法
@Test
public void testSelectById() thorws IOException{
    // 接收参数
    int id = 1;
    ...SqlSessionFactory...
    // 执行方法
    Brand brand = brandMapper.selectById(id);
    System.out.println(brand);
}
  1. 特殊字符的处理

转移字符: &lt CDATA区:写一个CD,会提示出来

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

图片.png

条件查询

通过输入的条件,来进行模糊查询

MyBatis如何接收多种参数、有3种方法

图片.png

  1. 先写SQL语句
<select id = "selectByCondition" resultMap = "brandResultMap">
    select *
    from tb_brand
    where status = #{status}
        and company_name like #{companyName}
        and brand_name like #{brandName}
</select>
  1. 书写映射接口:三种接收方式: 01: 散装参数接收:如果方法中有多个参数,需要使用@Param("SQL参数占位符名称")
List<Brand> selectByCondition(
    @Param("status")int status,
    @Param("companyName")String companyName,
    @Param("brandName")String brandName
    );

02:对象参数接收:对象的属性名要和参数占位符名称一致

List<Brand> selectByCondition(Brand brand);

03:map集合参数接收:map键的名称要和参数占位符名称对应上

List<Brand> selectByCondition(Map map);

3.1 散装参数测试用例:散装参数接收:如果方法中有多个参数,需要使用@Param("SQL参数占位符名称")

public void testSelectByCondition() throws IOException{
    //1.接收参数
    int status = 1;
    String companyName = "华为";
    String brandName = "华为";
    
    //2.处理参数,like#{..}是要有%的,我们在这里添加
    companyName = "%" + companyName + "%";
    brandName = = "%" + brandName + "%"; 
    
    .......获取SqlSessionFactory
    .......获取SqlSession对象
    .......获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
    
    //执行方法
    List<Brand> brands = brandMapper.selectByCondition(status,companyName,brandName);
    System.out.println(brands);
    
    //释放资源
    sqlSession.close();
}

3.2 对象参数测试用例:对象的属性名要和参数占位符名称一致

Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);

List<Brand> brands = brandMapper.selectByCondition(brand);
System.out.println(brands);

3.3 map集合参数测试用例:map键的名称要和参数占位符名称对应上

Map map = new HashMap();
map.put("status",status);
map.put("companyNme",companyNme);
map.put("brandName",brandName);

图片.png

动态条件查询

上面的例子中,如果我用户并没有输入值,那么参数就会对应不上,就会存在bug。

图片.png

通过动态SQL

图片.png

动态条件查询
<select id = "selectByCondition" resultMap = "brandResultMap">
    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>

单条件的动态查询

图片.png

//单条件动态查询
List<Brand> selectByConditionSingle(Brand brand)
<select id = "selectByConditionSingle" result = "brandResultMap">
    select * 
    from tb_brand
    <where>
        <choose>//相当于switch
            <when test="status!=null">//相当于case
                stateu = #{status}
            </when>
            <when test="companyName!=null and companyName!=''">//相当于case
                companyName = #{companyName}
            </when>
            <when test="brandName!=null and brandName!=''">//相当于case
                brandName = #{brandName}
            </when>
        </choose>
    </where>
</select>
public void testSelectByConditionSingle() throws IOException{
    Brand brand = new Brand();
    brand.setStatus(status);
    ...
    brandMapper.selectBy
    
}

添加

图片.png

如果数据发生变化的,需要进行提交事务!

添加功能
void add(Brand brand);
<insert id = "add">
    insert into tb_brand(brand_name,company_name,ordered,description,status)
    values(#{brandName},#{companyName},#{ordered},#{description},#{status});
    
</add>
public void testAdd() throws IOException{
    //接收参数
    int status = 1
    String companyName = "";
    String brandName = "";
    String description = "";
    int ordered = 100;
    
    //封装对象
    Brand brand = new Brand();
    brand.setStatus(status);
    brand.setCompanyName(companyName);
    brand.setBrandName(brandName);
    brand.setDescription(description);
    brand.setOrdered(ordered);
    
    ......老三步
    
    //执行方法
    brandMapper.add(brand)
    
    //如果数据发生变化的,需要进行提交事务!
    sqlSession.commit();
    
    //关闭资源
    sqlSession.close();
}

或者在获取SqlSession的时候进行设置自动事务提交

图片.png

添加之主键返回

添加之后,我还想单独获取到id主键的值;
有可能多表的时候,其中一个表的主键是另一个表的外键,如果获取不到,另一表是会存在bug的
通过useGenerateKeys="true" keyProperty = "接口方法中的主键">

图片.png

修改

修改全部字段

图片.png

修改动态字段

图片.png

提供set标签,防止语法错误

图片.png

删除

删除一个

根据id删除

图片.png

图片.png

批量删除

图片.png

用数组来存储要删除的id,并且where语句中的数量也是动态变化的

提供了foreach标签来对数组、集合进行遍历;

collection属性:要遍历的数组的名字

item属性:遍历出来的每一个元素

myBatis会将数组参数封装成一个Map集合,Map集合就表示:有key、有value;
默认情况下:key的名称:array;value就是对应的数组
如果不想叫array,那么就需要通过@Params进行设置

separator属性:表示用什么符号进行分割

//void deleteByIds(int[] ids);
void deleteByIds(@Params("ids") int[] ids);
<delete id = "deleteByIds">
    delete from tb_brand where id
    in(
        //<foreach collection = "array">
        <foreach collection = "ids" item = "id" separator=",">
            #{id}
        </foreach>
    )

</delete>

open属性:拼接开始的字符串 close属性:拼接结束的字符串

图片.png

public void testDeleteByIds() where IOException{
    //接收参数
    int[] ids = {5,7,8};
    ...
    //执行方法
    brandMapper.deleteByIds(ids);
}

参数传递

多个参数,要使用@Params【在接口中书写的】:和SQL注入参数保持一致。

图片.png

注解开发

以前我们将SQL语句写在Mapper.xml中,现在我们写在类中。

简单的语句用注解,复杂的语句(动态SQL的)用xml配置 图片.png

图片.png

图片.png

补充

多表查询

Order.java

private int id;
private Date ordertime;
private double total;
private User user;

这个是OderMapper.xml,这里的id与OrderMapper接口中的所要select的方法名要相同,resultType通过别名设置,可以使用别名书写。

<select id = "findAll" resultType = "order">
    selectsql语句
</select>

修改:

resultMap原本是resultType,但是这里需要的类型太多,所以需要通过resultMap来书写

<resultMap id = "orderMap" type = "order">
    手动指定字段与实体属性的映射关系
    主键需要单独通过id标签设置
    <id column = "oid" property = "id"></id>
    <result column = "ordertime" property = "ordertime"></result>
    <result column = "total" property = "total"></result>
    
    User比较特殊,是user实体的内值
    <association property = "user" javaType = "user">
        <id column = "uid" property = "(user)id"></id>
        <result column = "username" property = "username"></result>
        .......
    </association>
</resultMap>



<select id = "findAll" resultMap = "orderMap">
    select *,o.id oid from orders o,user u where o.uid = u.id
</select>

一对多:一个用户有多个订单,一个订单只从属于一个用户;查询一个用户,与此同时查询出该用户具有的(多个)订单

User.java

public List<Order> orderList;
public List<Order> getOrderList(){}
public void setOrderList(List<Order> orderList){}
<resultMap id = "userMap" type = "user">
    <id column = "uid" property = "id"></id>
    <result column = "username" property = "username"></result>
    ...
    <collection property = "orderList" ofType = "order">
        封装order的数据
        <id column = "oid" property = "id"></id>
        <result column = "ordertime" property = "ordertime"></result>
    </collection>
</resultMap>

测试类

InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml")
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.findAll();

for(User user : userList){
    
}

多对多(三张表,并且需要有一个中间表):查询用户同时查询出该用户的所有角色

图片.png

Role.java是实体类

User.java是实体类,在User实体类中书写集合存储Role

UserMapper.xml映射

<resultMap id = "userRoleMap" type = "user">
    user的信息
    <id column = "userId" property = "id"></id>
    <result column = "username" property = "username"></result>
    ...
    
    user内部的rolelist的信息
    <collection property = "roleList" ofType = "role"(要取别名)>
        <id column = "roleId" property = "id"></id>
        <result column = "roleName" property = "roleName"></result>
        ....
        
    </collection>
</resultMap>

<select id = "findUserAndRoleAll" resultMap = "userRoleMap">
    
</select>

查询出三张表中,id全部相同的信息 select * from user u,sys_user_role ur,sys_role r where u.id = ur.userId and ur.roleId = r.id

总结

<resultMap id = "orderMap" type = "order">:里面存储type属性中的存储信息,并且还有type属性中引用的其他实体类的信息

如果是一对一:<association property = "user" javaType = "user">...

注解

sqlMapConfig.xml

<mappers>
    <package name = "接口所在包"></package>
</mappers>

以前的写法:

<mappers>
    <mapper resource = "先加载接口的.xml"></maper>
</mappers>

图片.png

图片.png

OrderMapper.java:一对一

@Select("select *,o.id oid from orders o,user u where o.uid = u.id")
@Results([
    @Result(column = "oid",property = "id"),
    @Result(column = "ordertime",property = "ordertime"),
    @Result(column = "uid",property = "user.id"),
    @Result(column = "username",property = "user.username")
])
public List<Order> findAll();

一对一之association方式的改进:

@