V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
tamer
V2EX  ›  Java

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

  •  
  •   tamer · Jul 6, 2018 · 9985 views
    This topic created in 2852 days ago, the information mentioned may be changed or developed.

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

    select * ....

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

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

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

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

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

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

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

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

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

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

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

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

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

    PS:学到了楼上的一些方法了
    ren2881971
        36
    ren2881971  
       Jul 7, 2018
    自己定义 factory-class 然后写一个 baseRepository 在里面用 EntityManager 就可以直接写原生 sql 了,然后撸起袖子就是干。
    搜索关键词 jpa:repositories、factory-class、JpaRepositoryFactoryBean、JpaRepositoryFactory。
    ps:谁开发后台只用 select * 啊。 开玩笑。。
    ren2881971
        37
    ren2881971  
       Jul 7, 2018
    <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  
       Jul 7, 2018
    糟糕 没排版。。。
    dbpe
        39
    dbpe  
       Jul 7, 2018
    @ren2881971 贴个 github 的地址把。。
    tamer
        40
    tamer  
    OP
       Jul 7, 2018
    @ren2881971 感谢 , 主要是被 jpa 的方法名称推断惊艳到了, 连自动生成语句都帮程序员解决了所以就想用用, 实际除了最简单的 select 语句外, 我个人都基本倾向写原生 sql, 所有有这个问题
    ren2881971
        41
    ren2881971  
       Jul 7, 2018
    @dbpe https://github.com/ren2881971/spring4mvc-jpa-demo 好几年前搭建的。。。。
    ren2881971
        42
    ren2881971  
       Jul 7, 2018
    @tamer 估计是你用 mybatis 用习惯了~ 如果一直用 hibernate 的话 没什么感觉~
    nicevar
        43
    nicevar  
       Jul 7, 2018
    配置一个 JpaTransactionManager 获取 EntityManager 直接操作原生 sql
    beginor
        44
    beginor  
       Jul 7, 2018 via Android
    JPA 可以认为就是 Hibernate, 我在 .Net 上用的是 NHibernate, 也有类似这样的需求, 复杂的查询用 SQL 解决, 在 .Net 下有现成的轮子 Dapper https://github.com/StackExchange/Dapper,Java 上应该也有类似 Dapper 的轮子吧?
    beginor
        45
    beginor  
       Jul 7, 2018 via Android
    稍微不注意排版就错误,Dapper 的地址是 https://github.com/StackExchange/Dapper。

    V 站啥时候回复也支持 md 呢?
    a812159920
        46
    a812159920  
       Jul 7, 2018
    EntityManager API 提供了创建 Query 实例以执行原生 SQL 语句的 createNativeQuery 方法。
    链接: https://blog.csdn.net/opera95/article/details/78207416
    不谢
    mmdsun
        47
    mmdsun  
       Jul 8, 2018 via Android
    @a812159920 这个原生 SQL 查询非 select * 返回的是 list<object[]>感觉好麻烦。不能直接返回 list<user>,然后没有查的字段返回 null 么。
    dbpe
        48
    dbpe  
       Jul 8, 2018
    @beginor 因为 hibernate 实现 jpa 的标准。。。
    reAsOn
        50
    reAsOn  
       Jul 9, 2018
    @ke1e 看你用什么版本的数据库了,和 jpa 关系不大,如果是新的 比如 mysql 8 可以直接建倒序的索引,如果比较老,可以用虚拟列,把时间 neg 一遍然后建普通的正序索引
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   5452 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 97ms · UTC 09:33 · PVG 17:33 · LAX 02:33 · JFK 05:33
    ♥ Do have faith in what you're doing.