相信大多数人对这些框架都不陌生,有关他们之间的区别和优缺点在这里我就不做赘述了。详见
jdbc、jpa、spring data jpa、hibernate、mybatis之间的关系及区别
作者:迷路剑客
具体示例代码如下:
@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了)。
具体代码如下:
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分页的写法,反正大家自行取舍吧。
?其实这个问题描述不太准确,还是先看代码吧。
/**
* 将实体类作为返回类型
* @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);
其实,对于大多数人来说,这个问题其实很常见,也算不上什么问题,但是在这里我还是简单的补充一下:JPA在执行delete,update时需要添加事务(在删除方法上面加上@Transactional(rollbackFor = Exception.class)注解就行了)。
我们在建立多对多关系的时候,有时候会弹出这个异常,但是这个异常的提示容易让人混淆!然而这是因为在Java对象中,字段定义和Hibernate文件的不一样。如Java中用String,而hbm中用type= "long"。
有时我们在配置Hibernate文件的时候会出现一大堆各种其奇怪的问题,在这里我就不做过多描述了,具体要讲的是比较坑的地方!那就是配置完配置文件后,一定要手动清除一遍缓存(用Maven中的命令clear一下)!!!
因为本人使用Hibernate的时间不长,所以有关Hibernate的常见问题我就不做总结了,这里我找了两个博客,大家有需要的可以看一下:
Hibernate 常见异常处理(转帖)_invalidqueryexception: undefined column name-CSDN博客
作者:wangtianxiao_haha
Hibernate常见的20个问题_xjar 启动hibernate-CSDN博客
作者:carpetknight
通常情况下,我们取值用的都是#{}这种方式取值的,但是有时候,#{}取值会失效!是的,你没看错,会失效!!!
第一种情况:查询字段没有定义类型。
上代码:
?
<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博客
作者:卖柴火的小伙子
当我们没有做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>
大家很奇怪,为什么在使用selectOne方法时会报OOM呢?
其实当你去看看selectOne方法的源码的时候你就发现.....selectOne这玩意儿原来是在selectList的原有基础上进行封装的。当查询结果集过大的时候,不仅效率会很慢,有可能还会出现OOM。
有关其他的Mybatis的问题,我在这里就不做总结了,以上是我找到的有关Mybatis博客,大家根据需要可以自行查看。
Mybatis使用的常见问题_mybatis语法selectall用不了-CSDN博客
作者:一只努力study的程序猿
向着百万年薪努力的小赵
以上就是本人在实际项目中使用以上框架时所遇到的坑!欢迎大家补充和提问!