V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tamer
V2EX  ›  Java

关于 Spring data jpa 写原生 sql 的问题

  •  
  •   tamer · 2018-07-06 20:42:06 +08:00 · 9305 次点击
    这是一个创建于 2369 天前的主题,其中的信息可能已经有所发展或是发生改变。

    官网示例中的 @Query(nativeQuery=true) 原生 sql 语句都是

    select * ....

    , 结果自己用发现, select 部分字段的话, 缺失的字段会被抛出异常:... Column xxx not found.

    比如 User 包含 id 和 name, 如果我只 select id from ...., 就会报错. 然后 gg

    好不容易从 mybatis 切换到 data jpa, 结果最想要的功能没法用,

    stack, 百度 都找不到解决方案, 是 jpa 太小众了, 还是我搜索姿势不对....

    要是放弃了, 实在不甘心啊...

    有没有也遇到过此问题的道友, 抱拳了 老铁

    
    
    
    
    50 条回复    2018-07-09 09:57:12 +08:00
    tamer
        1
    tamer  
    OP
       2018-07-06 20:42:51 +08:00
    @glues 老铁
    AltairT
        2
    AltairT  
       2018-07-06 20:44:58 +08:00 via iPhone
    关注一下 感觉以后是主流技术了
    airfling
        3
    airfling  
       2018-07-06 20:49:58 +08:00 via Android
    部分字段只,如果只是一个 id 你可以用 list<idtype>,多的话可以用 list<map>
    shalk
        4
    shalk  
       2018-07-06 20:51:35 +08:00 via iPhone
    select user.id from user
    Sharuru
        5
    Sharuru  
       2018-07-06 20:55:20 +08:00 via Android
    通常是映射的结果集不正确。

    另外,JPA 应用一般都是选择实体,通过 Stream 取得自己需要的列。
    tamer
        6
    tamer  
    OP
       2018-07-06 21:01:48 +08:00
    @shalk 不行, 版本: Maven: org.springframework.data:spring-data-jpa:2.0.8.RELEASE
    tamer
        7
    tamer  
    OP
       2018-07-06 21:03:31 +08:00
    @Sharuru 官网给出的例子只有 select *, 如果我把所有列都放到 select 语句中就可以成功执行, 但如果缺少某某列, 就会报错,

    select user_id from : xx
    select user_id , username from : √√
    所以可以判断映射的字段是匹配的
    lhx2008
        8
    lhx2008  
       2018-07-06 21:04:21 +08:00 via Android
    可以返回一个 int 值再自己装?不知道有什么安全性考虑
    tamer
        9
    tamer  
    OP
       2018-07-06 21:07:41 +08:00
    @airfling map 确实可以映射, 但是除了直接从库中返回给前端的请求外, 涉及到对 User 的操作, map 就很蓝瘦了呀
    tamer
        10
    tamer  
    OP
       2018-07-06 21:09:06 +08:00
    @lhx2008 如果所有原生 sql 都必须这样手动封装一次....... 那 jpa 提供的一些蝇头小利真的被 mybatis 完爆啦
    lhx2008
        11
    lhx2008  
       2018-07-06 21:09:58 +08:00
    @tamer 但是全部拉出来并没有多多少资源,有洁癖还是用 mybatis 吧
    lhx2008
        12
    lhx2008  
       2018-07-06 21:12:09 +08:00
    我猜可能是如果有关联对象,会造成很严重的问题
    tamer
        13
    tamer  
    OP
       2018-07-06 21:13:29 +08:00
    @lhx2008 对于 json 字段, 取的话, 如果每次都必须强制获取全部, 还是很可怕的, 如果解决不了, 我还是只有换回 mybatis 啦
    lhx2008
        14
    lhx2008  
       2018-07-06 21:13:41 +08:00
    或者你重新封装一个类,只有 id,然后再让真正的 user 继承这个类,但是调用的时候也只能调用那个父类了
    chocotan
        15
    chocotan  
       2018-07-06 21:13:49 +08:00   ❤️ 1
    select 部分字段 返回值改成 List<Object[]>

    既然都是原生 sql 的话那用 jpa 的意义何在......
    lhx2008
        17
    lhx2008  
       2018-07-06 21:17:54 +08:00
    @tamer
    json 返回不是大问题,你本来就应该在 json 返回的时候重新封装一个对象,直出数据库真的好吗
    tamer
        18
    tamer  
    OP
       2018-07-06 21:28:11 +08:00
    @lhx2008 一般情况下, 对于有的属性 实体类则被赋值, 没有的属性自动置为 null, 这不是 orm 框架应该做的吗

    Hibernate 都可以自动映射, jpa 封装了 Hibernate 居然就不支持了...
    tamer
        19
    tamer  
    OP
       2018-07-06 21:29:15 +08:00
    @chocotan orm 自动映射的规则不是只要字段名一致, 有值就赋值, 没有值则为 NULL 吗? 现在 jpa 强制要求实体类每一个属性都必须有值, 这才是奇怪的地方把, Hibernate 和 Mybatis 都不是这样吧
    sagaxu
        20
    sagaxu  
       2018-07-06 22:07:35 +08:00 via Android   ❤️ 1
    jpa 的工作方式不同,你只能返回 List<Object[]>或者自己定义一个新类型。连返回 map 都不行。
    WispZhan
        21
    WispZhan  
       2018-07-06 22:25:42 +08:00 via Android   ❤️ 1
    你用错了。不是你这样用。repository 是针对 entity 的操作。看看 ddd 的设计思想在用 jpa。你会发现,你之前压根就没理解 jpa 的使用场景。

    你的 repository 是针对你那个 select * 的 entity 封装的。你要查询部分字段那就重做一个 entity 绑定到你需要的表上。可以理解为做了一个 view。
    ke1e
        22
    ke1e  
       2018-07-07 00:11:06 +08:00 via Android
    搭车问下 jpa 建索引如何建倒叙索引?想给时间字段建立倒叙索引
    wdlth
        23
    wdlth  
       2018-07-07 00:11:59 +08:00
    你需要用投影
    mmdsun
        24
    mmdsun  
       2018-07-07 00:57:33 +08:00 via Android
    告诉个黑科技。如果是 hql 用 new 包名.类名(构造函数)直接获取投影对象。原生 SQL 要写注解配置 mapper。 @SqlResultSetMappings 用列或构造函数映射类
    ZSeptember
        25
    ZSeptember  
       2018-07-07 02:40:19 +08:00 via Android
    querydsl
    kevinhwang
        26
    kevinhwang  
       2018-07-07 08:03:47 +08:00 via Android
    jpa 也就是做做简单的应用,过度强调 orm 性能和可维护性差。抛开性能讲可维护性,你能理解其他的程序员往你的 entity 加各种字段吗?
    mgcnrx11
        27
    mgcnrx11  
       2018-07-07 08:22:26 +08:00
    caotian
        28
    caotian  
       2018-07-07 09:10:47 +08:00
    弄个 Interface 比如叫 UserLite, 只有一个方法 getId(), 然后查询那里返回值用 List<UserLite>, 再 select id 就不会出错了
    letitbesqzr
        29
    letitbesqzr  
       2018-07-07 09:27:28 +08:00
    querydsl 试试
    srx1982
        30
    srx1982  
       2018-07-07 10:25:15 +08:00
    你看看实体上的字段是不是标了 @NotNull
    q397064399
        31
    q397064399  
       2018-07-07 11:00:56 +08:00   ❤️ 1
    JPA 是针对聚合实体的,,你那种 不要一部分字段. 我很难理解. , 你要精确控制这数据库这一小段 IO 的话
    就应该另外再建个实体 映射到同一张表上。而且从仓储层拿出来的对象 是不完整的话 也违反了最小惊讶原则,
    后面谁调你接口 一不小心就弄了 NPE 很尴尬,数据库都是不提倡使用 NULL 值的,

    如果只是取一部分数据,这个对象不用修改,那应该建模为值对象而不是实体。
    另外用 JPA 最好还是了解下 实体 聚合 值对象,JPA 就是为这玩意设计的。
    q397064399
        32
    q397064399  
       2018-07-07 11:04:29 +08:00
    另外用 JPA 就应该从 SQL 中跳出来,关注实体建模, 很多时候都是反模式
    yzmm
        33
    yzmm  
       2018-07-07 11:15:03 +08:00
    自定义 Repository 就可以了
    zxyroy
        34
    zxyroy  
       2018-07-07 11:34:03 +08:00
    如果你只 select id,返回值应该是 id,否则要给返回类设构造器。另外 JQL 是支持 new class 的
    dbpe
        35
    dbpe  
       2018-07-07 11:40:12 +08:00
    用 jpa 就不要用原生 sql 了。。另外试下 querydsl

    PS:学到了楼上的一些方法了
    ren2881971
        36
    ren2881971  
       2018-07-07 12:58:51 +08:00
    自己定义 factory-class 然后写一个 baseRepository 在里面用 EntityManager 就可以直接写原生 sql 了,然后撸起袖子就是干。
    搜索关键词 jpa:repositories、factory-class、JpaRepositoryFactoryBean、JpaRepositoryFactory。
    ps:谁开发后台只用 select * 啊。 开玩笑。。
    ren2881971
        37
    ren2881971  
       2018-07-07 13:03:07 +08:00
    <jpa:repositories base-package="com.jit.ota4.*.*.repository" repository-impl-postfix="Impl" factory-class="com.jit.ota4.basic.repository.BaseRepositoryFactoryBean" entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="transactionManager"/>


    public class BaseRepositoryFactoryBean<R extends JpaRepository<S, ID>, S, ID extends Serializable>
    extends JpaRepositoryFactoryBean<R, S, ID>{
    @Override
    protected RepositoryFactorySupport createRepositoryFactory(EntityManager e) {
    return new BaseRepositoryFactory(e);
    }
    }


    public class BaseRepositoryFactory<S,ID extends Serializable> extends JpaRepositoryFactory {
    public BaseRepositoryFactory(EntityManager entityManager) {
    super(entityManager);
    }

    @Override
    protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
    return BaseRepository.class;
    }

    @Override
    protected <T, ID extends Serializable> SimpleJpaRepository<?, ?> getTargetRepository(RepositoryInformation information, EntityManager entityManager) {
    return new BaseRepositoryImpl(information.getDomainType(), entityManager);
    }
    }


    public interface BaseRepository<T,ID extends Serializable> extends JpaRepository<T,ID>,JpaSpecificationExecutor<T> {


    /**
    * @param sql 原生 sql 语句
    * @param param 动态执行参数 type:List
    * @return 返回执行的结果集条数
    */
    public int executeUpdateBySQL(String sql,List<Object> param);
    /**
    * @param sql 原生 sql 语句
    * @param param 动态执行参数 type:Map
    * @return 返回执行的结果集条数
    */
    public int executeUpdateBySQL(String sql,Map<String,Object> param);
    /**
    * @param hql hql 语句
    * @param param 动态执行参数 type:List
    * @return 返回执行的结果集条数
    */
    public int executeUpdateByHql(String hql,List<Object> param);
    /**
    * @param hql hql 语句
    * @param param 动态执行参数 type:Map
    * @return 返回执行的结果集条数
    */
    public int executeUpdateByHql(String hql,Map<String,Object> param);

    /**
    * @param hql hql 语句
    * @param param 动态查询参数 type:List
    * @param t 单实例类型
    * @return 单实例结果集
    */
    public List<T> findByHql(String hql,List<Object> param,Class<T> t);

    /**
    * @param hql hql 语句
    * @param param 动态查询参数 type:List
    * @param t 单实例类型
    * @param pageNo 页码数
    * @param pageSize 每页条数
    * @return 单实例结果集
    */
    public List<T> findByHqlWithPage(String hql,List<Object> param,Class<T> t,int pageNo,int pageSize);

    /**
    * @param hql hql 语句
    * @param param 动态查询参数 type:List
    * @return 自定义字段返回结果集
    */
    public List<Object[]> findByHql(String hql,List<Object> param);

    /**
    * @param hql hql 语句
    * @param param 动态查询参数 type:List
    * @param pageNo 页码数
    * @param pageSize 每页条数
    * @return 自定义字段返回结果集
    */
    public List<Object[]> findByHqlWithPage(String hql,List<Object> param,int pageNo,int pageSize);

    /**
    * @param sql 原生 sql 语句
    * @param param 动态查询参数 type:List
    * @return 执行原生 sql 返回结果集
    */
    public List<Object[]> findBySQL(String sql,List<Object> param);

    /**
    * @param sql 原生 sql 语句
    * @param param 动态查询参数 type:List
    * @param pageNo 页码数
    * @param pageSize 每页条数
    * @return
    */
    public List<Object[]> findBySQLWithPage(String sql,List<Object> param ,int pageNo,int pageSize);


    }
    ren2881971
        38
    ren2881971  
       2018-07-07 13:04:14 +08:00
    糟糕 没排版。。。
    dbpe
        39
    dbpe  
       2018-07-07 13:29:36 +08:00
    @ren2881971 贴个 github 的地址把。。
    tamer
        40
    tamer  
    OP
       2018-07-07 13:50:19 +08:00
    @ren2881971 感谢 , 主要是被 jpa 的方法名称推断惊艳到了, 连自动生成语句都帮程序员解决了所以就想用用, 实际除了最简单的 select 语句外, 我个人都基本倾向写原生 sql, 所有有这个问题
    ren2881971
        41
    ren2881971  
       2018-07-07 14:26:18 +08:00
    @dbpe https://github.com/ren2881971/spring4mvc-jpa-demo 好几年前搭建的。。。。
    ren2881971
        42
    ren2881971  
       2018-07-07 14:27:45 +08:00
    @tamer 估计是你用 mybatis 用习惯了~ 如果一直用 hibernate 的话 没什么感觉~
    nicevar
        43
    nicevar  
       2018-07-07 17:52:27 +08:00
    配置一个 JpaTransactionManager 获取 EntityManager 直接操作原生 sql
    beginor
        44
    beginor  
       2018-07-07 18:01:01 +08:00 via Android
    JPA 可以认为就是 Hibernate, 我在 .Net 上用的是 NHibernate, 也有类似这样的需求, 复杂的查询用 SQL 解决, 在 .Net 下有现成的轮子 Dapper https://github.com/StackExchange/Dapper,Java 上应该也有类似 Dapper 的轮子吧?
    beginor
        45
    beginor  
       2018-07-07 18:03:13 +08:00 via Android
    稍微不注意排版就错误,Dapper 的地址是 https://github.com/StackExchange/Dapper。

    V 站啥时候回复也支持 md 呢?
    a812159920
        46
    a812159920  
       2018-07-07 20:52:25 +08:00
    EntityManager API 提供了创建 Query 实例以执行原生 SQL 语句的 createNativeQuery 方法。
    链接: https://blog.csdn.net/opera95/article/details/78207416
    不谢
    mmdsun
        47
    mmdsun  
       2018-07-08 00:31:40 +08:00 via Android
    @a812159920 这个原生 SQL 查询非 select * 返回的是 list<object[]>感觉好麻烦。不能直接返回 list<user>,然后没有查的字段返回 null 么。
    dbpe
        48
    dbpe  
       2018-07-08 00:48:10 +08:00
    @beginor 因为 hibernate 实现 jpa 的标准。。。
    reAsOn
        50
    reAsOn  
       2018-07-09 09:57:12 +08:00
    @ke1e 看你用什么版本的数据库了,和 jpa 关系不大,如果是新的 比如 mysql 8 可以直接建倒序的索引,如果比较老,可以用虚拟列,把时间 neg 一遍然后建普通的正序索引
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1117 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 39ms · UTC 23:04 · PVG 07:04 · LAX 15:04 · JFK 18:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.