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

JPA 问题请教

  •  
  •   reid2017 · 2018-11-14 18:05:38 +08:00 · 3502 次点击
    这是一个创建于 1982 天前的主题,其中的信息可能已经有所发展或是发生改变。
    @SQLInsert(sql = "insert ignore into tb_vip_code (code, duration) value (?, ?)")
    public class VipCode extends BaseModel {
        private static final long serialVersionUID = -4697221755301869573L;
    
        private String code;
        private Integer duration;
        private Integer status;
        private Long userId;
    
        // 构造函数
    }
    

    如上实体类定义,@SQLInsert 注解的本意是在批量插入数据遇到唯一性约束时忽略,继续插入不重复的数据,但在调用 repository 的 save 方法插入数据是,总是报参数越界错误,有朋友遇到过吗?

    Caused by: java.sql.SQLException: Parameter index out of range (3 > number of parameters, which is 2).

    单元测试代码如下:

    @Test
        public void addOne() throws Exception {
            VipCode vipCode = new VipCode("123456", 1);
            service.addOne(vipCode);
        }
    
    第 1 条附言  ·  2018-11-15 09:10:46 +08:00
    `BaseModel` 定义
    ```
    @Data
    @MappedSuperclass
    public class BaseModel implements Serializable {
    @Id
    @GeneratedValue
    private Long id;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Convert(converter = LocalDateTimeConverter.class)
    private LocalDateTime createAt = LocalDateTime.now();

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Convert(converter = LocalDateTimeConverter.class)
    private LocalDateTime updateAt = LocalDateTime.now();
    }
    ```
    第 2 条附言  ·  2018-11-15 11:59:37 +08:00

    第一个问题解决了,SQL 里要把除自增主键外的所有字段写全,而且顺序要跟实体类属性定义的顺序保持一致,父类的属性在最前面,后面接着是子类的属性,如下:

    insert ignore into tb_vip_code (create_at, update_at, code, duration, status, user_id) values (?, ?, ?, ?, ?, ?)

    但,又出现了另外一个问题,插入不重复记录的时候,可以正常插入数据,插入重复记录报异常:

    org.springframework.orm.jpa.JpaSystemException: The database returned no natively generated identity value

    欢迎大家继续讨论。

    22 条回复    2018-11-15 12:20:32 +08:00
    reid2017
        1
    reid2017  
    OP
       2018-11-14 18:07:55 +08:00
    自己 up 一下
    onnfee
        2
    onnfee  
       2018-11-14 18:41:17 +08:00
    你先找到 Hibernate 生成的 insert 语句,在此基础上 diy。
    johnniang
        3
    johnniang  
       2018-11-14 19:49:43 +08:00
    这个错误似乎是因为参数的问题。本来只需要两个参数,而 https://raymondhlee.wordpress.com/2012/01/07/using-sqlinsert-to-insert-entity-in-hibernate-with-custom-sql/
    johnniang
        4
    johnniang  
       2018-11-14 19:50:27 +08:00
    忽略 link 前面的文字 @reid2017
    Aidenboss
        5
    Aidenboss  
       2018-11-14 20:07:26 +08:00
    要不还是写在 VipCodeRepository 里面吧?
    ```
    interface VipCodeRepository {
    @Modify
    @Query(sql = "..." , nativeSql = true)
    @Transcational
    public void save(SString code, int duration);
    }
    ```
    sutra
        6
    sutra  
       2018-11-14 20:35:26 +08:00
    status 和 userId 字段是怎么定义的?还有 BaseModel 里面是不是有 id 字段的定义,怎么没有在 @SQLInsert 里出现呢?
    TommyLemon
        7
    TommyLemon  
       2018-11-14 23:45:53 +08:00
    @SQLInsert(sql = "insert ignore into tb_vip_code (code, duration) value (?, ?)")
    首先 value 得改成 values
    如果还不行,可能就是如楼上所说 BaseModel 里有额外的字段,很可能还是用基本类型有默认值的。
    如果是基本类型,改成对应的封装类型再试。

    另外你代码都没发全,都不知道构造函数里是否给其它字段 set 了值,难以判断。
    TommyLemon
        8
    TommyLemon  
       2018-11-14 23:46:17 +08:00
    @TommyLemon 默认值得去掉,或者改成 null。
    TommyLemon
        9
    TommyLemon  
       2018-11-14 23:51:47 +08:00
    @TommyLemon
    如果以上试过都不行,或许是 serialVersionUID 有个对应的 getSerialVersionUID 方法(自动生成时错误勾选),
    导致序列化时多转了一个字段。
    某些 JSON 库可以通过 @JSONField(serialize = false) 这种注解等方式来忽略要序列化的变量或方法。
    reid2017
        10
    reid2017  
    OP
       2018-11-15 09:12:24 +08:00
    @sutra
    status 数据库里有设置默认值,userid 可空,BaseModel 时有 id 主键自增,这些应该都不影响吧
    reid2017
        11
    reid2017  
    OP
       2018-11-15 09:13:52 +08:00
    @TommyLemon 把 serialVersionUID 注释掉也不行,构造函数里没有对其它字段设置值

    ```
    public VipCode(String code, Integer duration) {
    this.code = code;
    this.duration = duration;
    }
    ```
    reid2017
        12
    reid2017  
    OP
       2018-11-15 09:17:07 +08:00
    @johnniang 跟文章描述的场景并不一样
    reid2017
        13
    reid2017  
    OP
       2018-11-15 09:18:08 +08:00
    @Aidenboss 就是不想这样写啰,看到有这个注解应该试下
    TommyLemon
        14
    TommyLemon  
       2018-11-15 09:47:34 +08:00
    @reid2017 也有可能这个库默认加了一个 主键 或 创建时间 之类的字段,
    最好还是断点调试下下它最终到 JDBC 时 set 进去的值,这样最容易看出问题所在。
    sutra
        15
    sutra  
       2018-11-15 10:05:34 +08:00
    改成这样看看,换句话说就是把所有 @Column 都写全:
    @SQLInsert(sql = "insert ignore into tb_vip_code (id, userId, code, duration) value (?, ?, ?, ?)")
    passerbytiny
        16
    passerbytiny  
       2018-11-15 10:20:42 +08:00
    把 ID 的 @GeneratedValue 去掉试试,你用了自定义 SQL,GeneratedValue 很有可能被判定成由程序或序列自动生成(而不是数据库自增长)。
    ZiLong
        17
    ZiLong  
       2018-11-15 10:47:25 +08:00
    加日志看下最终执行的 sql 和对应的参数
    reid2017
        18
    reid2017  
    OP
       2018-11-15 11:01:56 +08:00
    @passerbytiny
    去掉这注解就需要自己手动给主键赋值了
    reid2017
        19
    reid2017  
    OP
       2018-11-15 11:06:33 +08:00
    @ZiLong
    Hibernate: insert ignore into tb_vip_code (code, duration) values (?, ?)
    参数没打印出来
    passerbytiny
        20
    passerbytiny  
       2018-11-15 11:23:17 +08:00
    差不多知道问题了,跟 ID 没关系,而是你的实体有 4 个非自动属性,SQl 语句只有两个字段。Hibernate 不会在手工 SQL 处理上放太多重心,这里可能只是简单做了实体属性跟 SQL 字段的映射,但是没有自动判断参数数量。

    你的 SQL 语句要把所有字段写全,另外除了“类属性——表字段”的自动映射外,不要期望 Hibernate 帮你做其它的自动处理。

    你现在这个是批量、仅部分字段、遇重复就忽略的操作,已经是一个很复杂的数据处理了,建议在 Dao/Repository,甚至 Service 中处理,并且全盘交给 SQL,不要再实体定义上处理。
    reid2017
        21
    reid2017  
    OP
       2018-11-15 12:05:58 +08:00
    主键 ID 策略如下:
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    //@GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    两个策略都尝试过,一样的异常。
    sutra
        22
    sutra  
       2018-11-15 12:20:32 +08:00
    把日志打开吧,然后就知道生成的 SQL 语句到底什么样子了:

    <Logger name="org.hibernate.SQL"
    level="DEBUG" includeLocation="true"
    additivity="false">
    <AppenderRef ref="file-hibernate" />
    </Logger>
    <Logger name="org.hibernate.type"
    level="TRACE" includeLocation="true"
    additivity="false">
    <AppenderRef ref="file-hibernate" />
    </Logger>
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5291 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 08:50 · PVG 16:50 · LAX 01:50 · JFK 04:50
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.