美文网首页ProQues
Mybatis开发中遇到的问题

Mybatis开发中遇到的问题

作者: 非典型_程序员 | 来源:发表于2019-06-29 20:00 被阅读0次

    最近在使用Mybatis的过程中遇到了一些问题,感觉有必要总结一下。前面一段时间简单的看了一点Mybatis的源码,对Mybatis在项目启动过程中以及启动后执行查询的工作流程有了一个大概的了解。这周开始我又到另一个项目,这是一个微服务项目,不过我只参与了一个服务的开发,项目使用的是Mybatis,但是并不是使用xml的方式,而是注解的方式,这也是我第一次在项目中使用注解的方式开发,中间也遇到了一些问题,这里做一个总结。
    其实在前面Mybatis源码分析(一)的过程中有提到过Mybatis的注解,但是我一直习惯使用xml方式,所以并没有专门去了解注解的方式。这次总结主要还是专注于使用中遇到的问题。

    一、一对多查询问题。

    开发这么久也是我第一次需要使用一对多查询,为了方便我自己写了一点简单的代码,来模拟这样一个问题。首先我有2个model,分别是order和product,从业务上来讲我一个订单可能有多个product,所以model的定义如下:

    @Data
    public class Order {
        private String userid;
    
        private String province;
    
        private String city;
    
        private String street;
    
        private String orderid;
    }
    
    @Data
    public class Product {
        private String orderid;
    
        private String productname;
    
        private Integer count;
    
        private String price;
    
        private String skucode;
    
    }
    @Data
    public class CustomOrder {
        private String userid;
    
        private String province;
    
        private String city;
    
        private String street;
    
        private String orderid;
    
        private List<String> productList;
    }
    

    CustomOrder中有一个productList的属性,存储的是某订单所有Product的skucode属性。当然productList也可以是所有商品的全部属性。

    1、使用Mapper.xml文件
    1.1 多表查询

    其实首先容易想到的就是关联表查询,sql如下:

    select o.*,p.skucode from t_order o left join t_product p on o.orderid = p.orderid  where o.userid = ?;
    

    查询的结果如下:


    图-1.png

    其实这个结果并不是我想要的,我希望最后一列的skucode多行数据转一行数据,即根据userid等列分组,也就是这种结果:


    图-2.png
    这个方式可以直接通过sql的方式实现,我们如果使用Mybatis的xml方式又该如何实现呢?其实查询的sql并不需要改变,只需要在进行结果映射的时候将查询的skucode放入到映射的model的productList属性即可,即映射到CustomOrder的productList属性中,xml如下:
    <mapper namespace="com.ypc.mybatis.mapper.OrderMapper">
      <resultMap id="BaseResultMap" type="com.ypc.mybatis.model.CustomOrder">
        <result column="userid" jdbcType="VARCHAR" property="userid" />
        <result column="province" jdbcType="VARCHAR" property="province" />
        <result column="city" jdbcType="VARCHAR" property="city" />
        <result column="street" jdbcType="VARCHAR" property="street" />
        <result column="orderid" jdbcType="VARCHAR" property="orderid" />
        <collection property="productList" ofType="string" javaType="list">
          <result column="skucode"/>
        </collection>
      </resultMap>
    
      <sql id="Base_Column_List">
        userid, province, city, street, orderid
      </sql>
    
      <select id="selectByUserId" resultMap="BaseResultMap">
        select o.*,p.skucode
        from t_order o left join t_product p on o.orderid = p.orderid where o.userid = #{userId}
      </select>
    
    </mapper>
    

    上面的xml中最主要的就是"<collection>"标签,"property"表示的是映射的model的属性,即CustomOrder的属性名称,ofType表示的是映射的集合中类型的名称,javaType表示的是集合类型,里面的还有多个标签,"<result>"标签中表示的是查询结果,这个需要sql中的列对应,比如sql中查询的列是skucode,那么"result"的column也必须是skucode。接下来通过一个接口测试一下,结果如下图:


    图-3.png


    下面是控制台输出的sql:


    图-4.png
    其实根据上图输出的sql就可以看出来,这种方式和我们一开始使用sql进行查询的结果是完全一样的(废话,sql的都一样,结果肯定一样),唯一的区别就是Mybati在输出结果的映射上帮我们进行了处理,即将skucode这一列的结果帮我们转成了一个List<String>。

    另外xml中除了"<collection>"标签,也可以使用"<association>"标签,这两个标签的作用应该是相同的。感兴趣的可以试一下。
    如果productList存放的就是Product的所有属性,CustomOrder的productList改为List<Product>。然后将sql和"<collection>"标签内容修改一下即可,xml修改如下:

    <mapper namespace="com.ypc.mybatis.mapper.OrderMapper">
      <resultMap id="BaseResultMap" type="com.ypc.mybatis.model.CustomOrder">
        <result column="userid" jdbcType="VARCHAR" property="userid" />
        <result column="province" jdbcType="VARCHAR" property="province" />
        <result column="city" jdbcType="VARCHAR" property="city" />
        <result column="street" jdbcType="VARCHAR" property="street" />
        <result column="orderid" jdbcType="VARCHAR" property="orderid" />
        <collection property="productList" ofType="com.ypc.mybatis.model.Product" javaType="list">
          <result column="orderid" property="orderid" jdbcType="VARCHAR"/>
          <result column="productname" property="productname" jdbcType="VARCHAR"/>
          <result column="count" property="count" jdbcType="INTEGER"/>
          <result column="price" property="price" jdbcType="VARCHAR"/>
          <result column="skucode" property="skucode" jdbcType="VARCHAR"/>
        </collection>
      </resultMap>
    
      <sql id="Base_Column_List">
        userid, province, city, street, orderid
      </sql>
    
      <select id="selectByUserId" resultMap="BaseResultMap">
        select o.*,p.*
        from t_order o left join t_product p on o.orderid = p.orderid where o.userid = #{userId}
      </select>
    

    再测试一次,结果如下:


    图-5.png

    根据上图的结果可以看出也映射成功了,这个方式其实和普通的关联表查询没有任何不同,唯一区别在于对查询结果的映射处理上。这是使用xml文件第一种方式。

    1.2 单表查询

    除了使用上面的关联查询之外还有另外一种方式,就是正宗的一对多查询。之所以这么说是因为我们上面只是将查询结果进行了不同的映射处理,本质上上面就是普通的关联查询。下面看看Mybatis的一对多到底是怎么样的,修改OrderMapper.xml文件如下:

    <mapper namespace="com.ypc.mybatis.mapper.OrderMapper">
      <resultMap id="BaseResultMap" type="com.ypc.mybatis.model.CustomOrder">
        <result column="userid" jdbcType="VARCHAR" property="userid" />
        <result column="province" jdbcType="VARCHAR" property="province" />
        <result column="city" jdbcType="VARCHAR" property="city" />
        <result column="street" jdbcType="VARCHAR" property="street" />
        <result column="orderid" jdbcType="VARCHAR" property="orderid" />
        <collection property="productList" column="orderid" select="com.ypc.mybatis.mapper.ProductMapper.selectByOrderId"/>
      </resultMap>
    
      <sql id="Base_Column_List">
        userid, province, city, street, orderid
      </sql>
    
      <select id="selectByUserId" resultMap="BaseResultMap">
        select *
        from t_order o where o.userid = #{userId}
      </select>
    

    根据上面的OrderMapper.xml文件可以看出,sql变成了单表查询,之查询t_order表,而在结果映射里面依然是使用"<collection>"标签,但是添加了一个"select"和"column"属性。先说"column"属性,这个表示的是"select"查询事需要传入的参数的列名,因Order和Product是通过orderid属性关联的,因此"column"的属性值就是orderid列;"select"表示的是执行的具体sql,因为我这里跨了不同的xml文件,所以必须使用namespace + id。需要在ProductMapper.xml添加一个查询方法,ProductMapper.xml如下:

    <mapper namespace="com.ypc.mybatis.mapper.ProductMapper">
      <resultMap id="BaseResultMap" type="com.ypc.mybatis.model.Product">
        <result column="orderid" jdbcType="INTEGER" property="orderid" />
        <result column="productname" jdbcType="INTEGER" property="productname" />
        <result column="count" jdbcType="INTEGER" property="count" />
        <result column="price" jdbcType="VARCHAR" property="price" />
        <result column="skucode" jdbcType="VARCHAR" property="skucode" />
      </resultMap>
    
      <sql id="Base_Column_List">
        orderid, productname, count, price, skucode
      </sql>
    
      <select id="selectByOrderId" resultMap="BaseResultMap">
        select * from t_product where orderid = #{orderId}
      </select>
    

    接下来我们再通过测试来检验一下结果,结果如下:


    图-6.png

    然后在看下控制台输出的sql是怎样的:


    图-7.png
    根据输出sql可以看出有两次查询,首先根据入参(userid)先查询t_order,然后用查询出来的orderid作为入参,执行第二个查询。感觉这里应该是一个查询嵌套,也就是说第二个查询返回结果之后,第一次查询才跟着结束,并返回结果集。而并不是第一个查询先结束再执行第二个查询。不知道这么理解对不对,关于一对多查询的问题就到这里,其实就两种方式来讲感觉关联查询更好一些,毕竟只需要查询一次数据库,第二种方式则需要查多次数据库,性能上可能会差一点。

    二、使用Mybatis注解

    其实我本身是不喜欢使用注解开发的,因为注解一样需要sql,一样需要做结果映射,显得代码会比较凌乱,但是因为架构定下的要求,所以自己还是要遵守的。下面说下注解开发遇到的一点小问题。

    1、使用注解一对多查询

    上面主要使用xml文件方式实现一对多查询,那么如果使用注解方式如何实现一对多呢??先修改相关的mapper文件,添加相关的注解,OrderMapper如下:

    public interface OrderMapper {
    
        @Results(id = "customOrder",value = {
                @Result(column = "userid",property = "userid",javaType = String.class,jdbcType = JdbcType.VARCHAR),
                @Result(column = "orderid",property = "orderid",javaType = String.class,jdbcType = JdbcType.VARCHAR),
                @Result(column = "province",property = "province",javaType = String.class,jdbcType = JdbcType.VARCHAR),
                @Result(column = "city",property = "city",javaType = String.class,jdbcType = JdbcType.VARCHAR),
                @Result(column = "street",property = "street",javaType = String.class,jdbcType = JdbcType.VARCHAR),
                @Result(column = "orderid",property = "productList",many = @Many(select = "com.ypc.mybatis.mapper.ProductMapper.selectByOrderId",fetchType = FetchType.EAGER))
        })
        @Select("select * from t_order o where o.userid = #{userId}")
        CustomOrder selectByUserId(@Param("userId") String userId);
    
    }
    

    同样我需要在ProductMapper文件添加一个相关的sql,代码如下:

    public interface ProductMapper {
    
        @Results(id = "product",value = {
                @Result(column = "orderid",property = "orderid",javaType = String.class,jdbcType = JdbcType.VARCHAR),
                @Result(column = "productname",property = "productname",javaType = String.class,jdbcType = JdbcType.VARCHAR),
                @Result(column = "count",property = "count",javaType = Integer.class,jdbcType = JdbcType.INTEGER),
                @Result(column = "price",property = "price",javaType = String.class,jdbcType = JdbcType.VARCHAR),
                @Result(column = "skucode",property = "skucode",javaType = String.class,jdbcType = JdbcType.VARCHAR)
        })
        @Select("select * from t_product where orderid = #{orderId}")
        List<Product> selectByOrderId(@Param("orderId") String orderId);
    
        @ResultMap("product")
        @Select("select * from t_product where skucode = #{skucode}")
        Product selectBySkuCode(@Param("skucode")String skucode);
        
    }
    

    在注解中,@Results注解和xml中的"resultMap"标签作用是相同的,主要就是实现表的列名和model的属性进行映射,而且这个结果也是可以共用的,比如上面的ProductMapper中selectBySkuCode方法,只需要指定结结果集的id就可以了。@Result注解相当与xml中的"result"标签一样,主要是针对某一列的映射关系。使用注解方式实现一对多的时候不能使用多表查询。所以注解实现一对多查询只有一种方式,就是OrderMapper中的方法。这里就不再测试了,因为和xml完全相同,如果测试的话记得注掉配置文件中的的xml的位置配置,即不让程序读取xml文件。

    2、注解中使用动态sql
    2.1使用<script>动态sql

    这个问题也是我在开发中遇到的一个问题,在xml开发中如果使用动态的sql可以使用的标签,比如"<if>"、"<foreach>"、"<choose>"等,但是如果在注解中又该怎么写动态sql呢?比如我在order表添加一个列status,同时在model类也添加一个对应的属性status。如果要在注解中使用动态sql,则必须使用"<script>"标签包裹对应的sql,比如order添加了一个根据user_id(为了表结构进行了调整)和status列去查询,那么mapper添加一个方法如下:

        @Select("<script>" +
                "select user_id as userid,order_id as orderid,province as province,city as city,street as street,status as status " +
                "from t_order where user_id = #{userid} " +
                "<if test='status != null and status !=\"\"'>" +
                "and status = #{status}" +
                "</if>" +
                "</script>")
        List<Order> queryByUserIdAndStatus(@Param("userid") String userid, @Param("status") String status);
    

    "<script>"标签包裹的内容和xml使用动态sql其实是一样的,这里需要注意一下就是有的会使用带大括号的方式,如下:

        //使用"+"
        @Select({"<script>" +
                "select user_id as userid,order_id as orderid,province as province,city as city,street as street,status as status " +
                "from t_order where user_id = #{userid} " +
                "<if test='status != null and status !=\"\"'>" +
                "and status = #{status}" +
                "</if>" +
                "</script>"})
    
        // 使用","
        @Select({"<script>",
                "select user_id as userid,order_id as orderid,province as province,city as city,street as street,status as status ",
                "from t_order where user_id = #{userid} ",
                "<if test='status != null and status !=\"\"'>",
                "and status = #{status}",
                "</if>",
                "</script>"})
    

    如果是这种方式我不太建议使用 "+"分割sql,因为我在开发中遇到过使用"+"报错的问题,改成","就没问题了,但是刚才在我电脑上使用也是正常的,不知道是不是因为Mybatis版本问题,因为我在开发中确实遇到过使用"+"sql异常改成","正常的情况,这里只是提醒一下可能会出现问题,也算是一种解决问题的思路吧。

    2.2 使用Provider实现

    关于动态sql除了直接使用"script"之外,Mybatis还提供了使用Provider的方式,将我们的mapper文件修改如下:

        @SelectProvider(type = OrderProvider.class,method = "queryByUserIdAndStatus")
        List<Order> queryByUserIdAndStatus(@Param("userid") String userid, @Param("status") String status);
    

    @SelectProvider表示的是执行查询的提供者,即sql提供方,需要注意的一点是,提供的sql并不是真正意义上的sql脚本,而是Mybatis处理之前的sql,即有OGNL表达式的sql,这点需要注意。然后创建一个queryByUserIdAndStatus方法,这个方法返回结果就是所说的sql,返回类型是String,代码如下:

    public class OrderProvider {
    
        public String queryByUserIdAndStatus(String userid,String status) {
            SQL sql = new SQL();
            sql.SELECT("user_id as userid,order_id as orderid,province as province,city as city,street as street,status as status");
            sql.FROM("t_order");
            sql.WHERE("user_id = #{userid}");
            if (status != null && status != "") {
                sql.WHERE("status = #{status}");
            }
            System.out.println("sql: " + sql.toString());
            return sql.toString();
        }
    }
    

    Provider的方法主要其实就是针对动态sql的问题,它本身不进行赋值,只是对传入的参数进行一些判断从而决定具体执行的sql而已。这里就不再进行测试了,感兴趣的可以自己试一下。


    三、总结

    关于自己开发中遇到的一点问题就到这里了,当然这只是工作的一部分,还有些知识点是自己没有涉及到的,只能有需要的时候再去学习吧。通过自己在实际开发中遇到的问题,以及自己不断查找资料解决,也不断刷新自己对Mybatis的认识。作为一个应用广泛的ORM框架,Mybatis确实非常的优秀。自己以前确实有很多方面没有涉及到,这次开发感觉收获还是蛮大的(不止是Mybatis),另外还有像mysql的一些处理json类型函数等等。关于一对多查询,除了上面的所说的方法之外还有其他的方式也可以达到这个目的,使用数据库的内置函数,但是这个方法相对比较麻烦,需要自定义TypeHandler,有兴趣的可以试试。


    最后:自己在微信开了一个个人号:超超学堂,都是自己之前写过的一些文章,另外关注还有Java免费自学资料,欢迎大家关注。

    二维码.jpg

    相关文章

      网友评论

        本文标题:Mybatis开发中遇到的问题

        本文链接:https://www.haomeiwen.com/subject/adxpcctx.html