项目当中使用JPA、Hibernate、MyBatis中遇到的坑

JPA、Hibernate、MyBatis之间的关系和区别

相信大多数人对这些框架都不陌生,有关他们之间的区别和优缺点在这里我就不做赘述了。详见

jdbc、jpa、spring data jpa、hibernate、mybatis之间的关系及区别

作者:迷路剑客

JPA在项目中使用时遇到的坑

一.在JPA中使用自定义的SQL

1.在DAO层加注解

具体示例代码如下:

 @Query(value = "select * from table1 " +
            "where col1 = :condition1 " +
            "and col2 = :condition2 ", nativeQuery = true)
 List<Entity> findAllByCondition1AndCondition2(String condition1, String condition2);
@Query(value = "select a.col1 from table1 a " +
            "left join table2 b on a.col1 = b.col1 " +
            "where a.col2 like concat('%', :condition1 ,'%')")
List<String> findCol1ByTable1AndTable2(String condition1);

这种自定义查询sql方式是比较常见的一种,当然你也可以在后面加上其他条件,甚至可以加上连接查询条件,但是前提是查询的出来的结果要和你实体类中的字段对应,不然就会报错!!!

这时就有人要问了,如果我在实体类上没有加@Column注解,或者是加上了@Transient注解,我又想使用这种查询方式怎么办呢?

好,重点来了(这是JPA比较坑的一点),上代码!

@Query(value = "SELECT " +
            " count(colCount1) AS colCount1  " +
            " count(colCount2) AS colCount2  " +
            "FROM " +
            " table1  " +
            "WHERE " +
            " condition1 = :condition1",
            nativeQuery = true)
List<Map<String, Object>> selectCountByCondition1(String condition1);

细心的朋友发现了,为什么这里要用List<Map<String, Object>>接收返回结果呢,因为数据库的数据呢?

因为在JPA中,当需要执行自定义的SQL查询时,通常会使用EntityManager的createNativeQuery方法。这个方法返回的是一个NativeQuery对象,它是通过getResultList方法来获取查询结果。

由于自定义的SQL查询可能会返回不确定数量和类型的列,因此无法直接映射到一个具体的实体类。因此,通常会使用List<Map<String, Object>>来接收返回结果,其中List表示查询返回的多行数据,而Map<String, Object>表示每一行数据的列名和对应的值。这种方式虽然灵活,但也增加了在代码中处理结果集的复杂度,需要我们手动解析和处理返回的数据(遇上需要查询列多的时候,简直就是折磨)。

你以为到这儿坑就填完了吗?ok,我们接着往下看

当你用数据库里面用到了tinyint类型的字段的时候,有趣的事情来了,你猜猜咋们的Object取出来的值会是什么?0,1,2,3······?no,no,no它会变成true,false,2,3!!!是的你没看错,他就是值就是这样的!!!那为什么呢?

因为在MySQL中,tinyint类型通常用于存储布尔值,其中0表示false,1表示true。当使用JPA执行自定义SQL查询用List<Map<String, Object>>接收返回结果时,tinyint字段的值会被映射为Java中的Boolean类型,即true或false。这是因为JPA会根据查询返回的数据类型进行自动映射,将tinyint字段的值转换为对应的Boolean类型。

其实问题到这儿,或许大部分人都知道该咋解决了(改一下数据库字段类型,或者用SQL函数CONVERT就解决了)

那么有没有另外的一种方式能够写自定义的SQL呢?有!但是这里不太推荐(因为太low了)。

2.在代码里写SQL

具体代码如下:

public class SelectDemo {
    public static void main(String[] args) {
        //查询条件
        Map<String, Object> conditionMap = new HashMap<>();
        conditionMap.put("col3","123");
        buildRelust1(buildCount(conditionMap));
    }

    @PersistenceContext(unitName = "MasterPersistenceUnit")
    public EntityManager em;

    public List<Object[]> buildCount(Map<String, Object> conditionMap) {
        StringBuilder countMinuteSql = new StringBuilder(" SELECT  " +
                "CONVERT " +
                " ( a.col1, UNSIGNED ), " +
                "coalesce( count( a.col2 ) ,0) ,1" +
                "FROM " +
                " table1 AS a " +
                " INNER JOIN table2 AS b ON a.col1 = b.clo1 " +
                "WHERE " +
                " 1 = 1  ");
        if (conditionMap.containsKey("col3") && conditionMap.containsKey("col4")) {
            countMinuteSql.append(" AND a.clo3>=:col3 AND a.clo3<=:col4");
        }
        if (conditionMap.containsKey("col5")) {
            countMinuteSql.append(" and b.col5 = (:col5)");
        }
        countMinuteSql.append(" GROUP BY a.col1");
        Query query = em.createNativeQuery(countMinuteSql.toString());
        conditionMap.forEach(query::setParameter);
        return query.getResultList();
    }

    /**
     * 自定义处理返回结果Map<Integer, List<Integer>>
     *
     * @param resultList 结果集
     * @return
     */
    public static Map<Integer, List<Integer>> buildRelust1(List<Object[]> resultList) {
        Map<Integer, List<Integer>> dataMap = new HashMap<>();
        resultList.forEach(result -> {
            // 第一列
            Integer userType = (Integer) result[0];
            // 第二列
            Integer count = (Integer) result[1];
            // 第三列
            Integer soothSub = (Integer) result[2];
            List<Integer> list = dataMap.getOrDefault(userType, new ArrayList());
            list.add(count);
            list.add(soothSub);
            dataMap.put(userType, list);
        });
        return dataMap;
    }

    /**
     * 自定义处理返回结果List<List<Integer>>
     *
     * @param resultList 结果集
     * @return
     */
    public static List<List<Integer>> buildRelust2(List<Object[]> resultList) {
        List<List<Integer>> dataMap = new ArrayList();
        resultList.forEach(result -> {
            // 第一列
            Integer userType = (Integer) result[0];
            // 第二列
            Integer count = (Integer) result[1];
            List<Integer> list = new ArrayList();
            list.add(userType );
            list.add(count );
            dataMap.add(list);
        });
        return dataMap;
    }

看出来问题在哪儿了吗?这玩意儿查出来的数据只能一列一列去解析,不是像传统的方式那样一行一行的去解析,给人的感觉就很怪......而且,正经人谁会在代码里面写SQL啊(梦回JDBC时代)!但是在某些特定的场景下这种方式反而比上面那种在DAO层注解里面写SQL好用些......比如,当我们自己写的分页工具类不能满足我们的需求的时候,写一些带条件的分页查询,这种写法反而要好点(唉,一言难尽).......

有关分页查询具体的代码示例我就不展示了,可以参考这位老哥写的:JPA三种分页查询_jpa分页查询-CSDN博客

作者:小小小啊伟

在这里补充一句,其实还有一个更好的分页写法方案,那就是建视图(视图是可以映射到实体类的)。以上各种有关JPA分页的写法,反正大家自行取舍吧。

二.JPA使用删除或保存语句常见问题

1.删除语句时好时坏

 其实这个问题描述不太准确,还是先看代码吧。

 /**
     * 将实体类作为返回类型
     * @param col1
 */
User deleteByCol1(String col1);

 大家在使用此类型的删除方法的时候可能会遇到过这种情况:明明上一条数据可以删除,为什么下一条数据就报错了呢?其实这个和JPA删除机制有关。通常情况下,我们将某个字段作为删除条件的时候,JPA会现在数据库里面先用该条件查一边,如果说我们的删除条件能够查得到数据,那么他会先将查询数据作为删除条件再去执行一次删除命令。但是,如果这个时候我们没有用该条件查到数据,他就会报错。

解决方案也很简单,在该方法上面加上@Modifying注解,或者在此基础上加上@Query自己写一个
SQL

具体代码如下:

  @Modifying
  @Query(value = "delete from tableName where col1 in (:col1s)")
  List<User> deleteAllByCol1In(Iterable<String> col1s);
2.JPA使用删除或保存语句时报错

其实,对于大多数人来说,这个问题其实很常见,也算不上什么问题,但是在这里我还是简单的补充一下:JPA在执行delete,update时需要添加事务(在删除方法上面加上@Transactional(rollbackFor = Exception.class)注解就行了)

Hibernate在项目中使用时遇到的坑

一.Hibernate出现异常时注意事项

1.出现....... MyObject altered from X to X异常

我们在建立多对多关系的时候,有时候会弹出这个异常,但是这个异常的提示容易让人混淆!然而这是因为在Java对象中,字段定义和Hibernate文件的不一样。如Java中用String,而hbm中用type= "long"。

2.配置文件异常

有时我们在配置Hibernate文件的时候会出现一大堆各种其奇怪的问题,在这里我就不做过多描述了,具体要讲的是比较坑的地方!那就是配置完配置文件后,一定要手动清除一遍缓存(用Maven中的命令clear一下)!!!

 二.Hibernate常见问题

因为本人使用Hibernate的时间不长,所以有关Hibernate的常见问题我就不做总结了,这里我找了两个博客,大家有需要的可以看一下:

Hibernate 常见异常处理(转帖)_invalidqueryexception: undefined column name-CSDN博客

作者:wangtianxiao_haha

Hibernate常见的20个问题_xjar 启动hibernate-CSDN博客

作者:carpetknight

MyBatis在项目中使用时遇到的坑

一.xml文件取值问题

1.#{}和${}在MyBatis中取值问题

通常情况下,我们取值用的都是#{}这种方式取值的,但是有时候,#{}取值会失效!是的,你没看错,会失效!!!

第一种情况:查询字段没有定义类型。

上代码:
 

<select id="getUserById" resultType="User">
      SELECT * FROM users WHERE id = #{userId} AND name = #{name}
</select>

通常情况下我们这样#{value} 这样传是没问题的,但是如果参数多了,这样写就可能有问题了。当我们没有设置parameterMap的时候,需要在#{value}里面加上字段类型。

代码示例:

<select id="getUserById" resultType="User">
      SELECT * FROM users WHERE id = #{userId,jdbcType.CHAR} AND name = #{name,jdbcType.CHAR}
</select>

当然你可以设置一个parameterMap去定义你的入参类型。

第二种情况:使用Map<String, Object>作为入参

上代码:

   public static void main(String[] args) {
        String value1 = "123";
        String value2 = "咸鱼翻身";
        Map<String, Object> map=new HashMap<String,Object>();
        map.put("value1", value1);
        map.put("value2",value2);

    userDAO.selectAllByIdAndName(map);
    }

<select id="selectAllByIdAndName" parameterType="java.util.Map">
    select * from user WHERE  id = '#{value1}' and name ='#{value2}'
</select>

这个时候,这种写法就会报错!!许多小伙伴就会问为什么了(这个我们后面会提到)。

正确的写法是将#{value1}换成${value1}

第三种情况:将入参作为SQL语句(不推荐此种写法)

先看代码:

 public static void main(String[] args) {
        String value1 = "name";
        Map<String, Object> map=new HashMap<String,Object>();
        map.put("value1", value1);
        userDAO.selectAllUser(map);
    }

<select id="selectAllUser" resultType="map">
    select * from user order by #{value1} desc
</select>

这种情况下,#{value}取值也会失效。必须将#{value}改成${value},如果真的要写这种动态条件,建议的方式是在业务层或者DAO层加上一些变量,然后再在Mapper文件里面写if条件,虽然这种方式有点麻烦,但是这种方式可以防止被SQL注入。

ok,说了以上的三种情况,我们再来看看为什么#{value}取值会失效。

在MyBatis中,#{}和${}都是用于取值的标记,但它们之间有一些重要的区别。

1). #{}:#{}是用于预编译的参数标记,它会将传入的参数值转义并进行预编译,可以防止SQL注入攻击。在执行SQL语句时,#{}会被替换成一个问号(?),并将参数值作为预编译的参数传递给数据库。这样可以有效防止SQL注入攻击,并且能够正确处理参数值中的特殊字符。

代码示例:

<select id="getUserById" parameterType="int" resultType="User">
  SELECT * FROM users WHERE id = #{userId}
</select>

2). ${}:${}是用于字符串替换的标记,它会将传入的参数值直接拼接到SQL语句中,不会进行预编译。在执行SQL语句时,${}会被替换成实际的参数值,这样可能会存在SQL注入的风险,因为参数值会直接拼接到SQL语句中,而不会进行转义处理。

代码示例:

<select id="getUserByName" parameterType="String" resultType="User">
  SELECT * FROM users WHERE name = '${userName}'
</select>

因此,使用#{}可以提高SQL的安全性,避免SQL注入攻击,推荐在编写MyBatis的SQL语句时使用#{}来传递参数值。而${}适合用于传递动态的表名、列名等场景,但需要格外注意防范SQL注入攻击。

想要了解更多关于#{}和${}实现方式和原理,可以看看这位老哥写的

mybatis中${}和#{}源码分析_mybatis #{} 源码-CSDN博客

作者:卖柴火的小伙子

2.#{}取不到值

当我们没有做parameterMap配置的时候,这个时候我们在编写DAO层方法的时候一定要注意,最好是在方法的入参前面加上@Param注解。

代码如下:

    List<User> selectAllByValue1AndValue2(@Param("value1") String value1,@Param("value2") String value2);

<select id="selectAllByValue1AndValue2" resultType="User" >
    select * from User where value1=#{value1} and
    value2=#{value2}
</select>

还有一种情况就是用like查询的时候也会出现取不到值的情况,这个时候我们将' 换成"就行了。

<select id="selectAllByValue1Like" resultType="User" >
    select * from User where value1 like "%"#{value1}"%"
</select>

二.MyBatis常见问题

1.有关selectOne报OOM错误

大家很奇怪,为什么在使用selectOne方法时会报OOM呢?

其实当你去看看selectOne方法的源码的时候你就发现.....selectOne这玩意儿原来是在selectList的原有基础上进行封装的。当查询结果集过大的时候,不仅效率会很慢,有可能还会出现OOM。

有关其他的Mybatis的问题,我在这里就不做总结了,以上是我找到的有关Mybatis博客,大家根据需要可以自行查看。

Mybatis使用的常见问题_mybatis语法selectall用不了-CSDN博客

作者:一只努力study的程序猿

Mybatis配置文件——全配置解析-CSDN博客

向着百万年薪努力的小赵

以上就是本人在实际项目中使用以上框架时所遇到的坑!欢迎大家补充和提问!

  • 35
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
HibernateMyBatisJPAMyBatis-Plus 都是 Java 比较流行的 ORM 框架。下面针对每个框架进行简单介绍和优缺点分析: 1. Hibernate Hibernate 是一个 ORM 框架,它可以将 Java 对象映射到关系型数据库的表上。Hibernate 通过提供面向对象的 API,简化了数据库编程,让开发者可以更加专注于业务逻辑实现。Hibernate 还提供了缓存、事务管理等功能。 优点: - 提供了 ORM 映射机制,简化了数据库访问操作。 - 支持面向对象的编程方式。 - 提供了缓存机制,提高了应用程序的性能。 - 提供了事务管理机制,保证了数据的一致性和完整性。 缺点: - 学习和使用成本较高。 - 如果映射关系不合理,可能导致性能问题。 - 由于缓存机制和事务管理机制的存在,对于一些高并发的场景,需要进行一定的优化。 2. MyBatis MyBatis 是一个基于 SQL 映射的 ORM 框架,它允许开发者使用 SQL 语句来操作数据库。MyBatis 通过提供简单易用的 SQL 映射配置文件,将 Java 对象映射到关系型数据库的表上。 优点: - 灵活性高,可以自由编写 SQL 语句,不受 ORM 映射机制的限制。 - 易于控制 SQL 执行过程,可以根据情况进行优化,提高性能。 - 学习和使用成本较低。 缺点: - 编写 SQL 语句需要一定的 SQL 知识。 - 不支持面向对象的编程方式。 - 不提供缓存和事务管理机制,需要开发者自行实现。 3. JPA JPAJava Persistence API)是 Java 的一种 ORM 规范,它定义了一套标准 API,用于操作数据库。JPA 的实现包括 Hibernate、EclipseLink 等。 优点: - 与 Hibernate 等 ORM 框架相比,JPA 的学习和使用成本较低。 - 支持面向对象的编程方式。 - 可以通过 JPA 规范来实现 ORM 映射,提高了代码的可移植性。 缺点: - JPA 规范的灵活性不如 Hibernate 等 ORM 框架。 - 由于规范的限制,JPA 在一些特殊场景下可能难以实现。 4. MyBatis-Plus MyBatis-Plus 是 MyBatis 的增强工具包,提供了一些方便的功能,如自动生成代码、分页查询、逻辑删除等。 优点: - 提供了一些方便的功能,提高了开发效率。 - 支持 MyBatis 的所有特性,具有良好的灵活性。 - 社区活跃,有较好的支持和文档。 缺点: - 功能较为简单,对于一些复杂的场景可能不够灵活。 - 相对于 MyBatisMyBatis-Plus 的学习成本稍高。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值