Spring-Data:生成的值会影响更新实体而不是插入

时间:2016-09-18 13:26:39

标签: spring jpa spring-data

有一种我不理解的行为。

我必须使用以下实体:

@Entity
@EqualsAndHashCode(of={"name"})
@ToString(of={"name", "entityB"})
public class EntityA {

    @Id
    @GeneratedValue
    @Getter
    @Setter
    private Long id;

    @Getter
    @Setter
    @Column(nullable=false, unique=true)
    private String name;

    @Getter
    @Setter
    @ManyToOne(fetch=EAGER, cascade={PERSIST, MERGE})
    private EntityB entityB;

}

@ToString(of = { "name" })
@EqualsAndHashCode(of = { "name" })
@Entity
class EntityB {

    @Id
    @GeneratedValue
    @Getter
    @Setter
    private Long id;

    @Getter
    @Setter
    @XmlAttribute
    @Column(nullable=false, unique=true)
    private String name;
}

将数据插入db:

的逻辑
@Component
public class DatabaseInitializer implements InitializingBean {
    @Autowired EntityARepository repository; // Spring-Data CrudRepository!

    @Override
    public void afterPropertiesSet() throws Exception {
        final Set<EntityA> aEntities = createAEntities();
        repository.save(aEntities);

        Iterator<EntityA> iterator = repository.findAll().iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

    private  Set<EntityA> createAEntities() throws Exception {
        Set<EntityA> aEntities = new HashSet<>();
        aEntities.add(getFirstEntityA());
        aEntities.add(getSecondEntityA());
        return aEntities;
    }

    private EntityA getFirstEntityA(){
        EntityA a = new EntityA();
        a.setId(1L);
        a.setName("a-1");
        a.setEntityB(getFirstEntityB());
        return a;
    }   

    private EntityA getSecondEntityA(){
        EntityA a = new EntityA();
        a.setId(2L);
        a.setName("a-2");
        a.setEntityB(getFirstEntityB());
        return a;
    }

    //

    private EntityB getFirstEntityB() {
        EntityB b = new EntityB();
        b.setId(1l);
        b.setName("b-1");
        return b;
    }

}

启动应用程序时,我得到以下输出:

org.hibernate.SQL: select entitya0_.id as id1_0_1_, entitya0_.entityb_id as entityb_3_0_1_, entitya0_.name as name2_0_1_, entityb1_.id as id1_1_0_, entityb1_.name as name2_1_0_ from entitya entitya0_ left outer join entityb entityb1_ on entitya0_.entityb_id=entityb1_.id where entitya0_.id=?
org.hibernate.SQL: select entityb0_.id as id1_1_0_, entityb0_.name as name2_1_0_ from entityb entityb0_ where entityb0_.id=?
org.hibernate.SQL: insert into entityb (id, name) values (default, ?)
org.hibernate.SQL: insert into entitya (id, entityb_id, name) values (default, ?, ?)
org.hibernate.SQL: update entitya set entityb_id=?, name=? where id=?

EntityA(name=a-1, entityB=EntityB(name=b-1))

如您所见,它更新了entitya而不是向db添加新行。

当我从两个实体中移除@GeneratedValue时,它就可以工作。

org.hibernate.SQL: select entitya0_.id as id1_0_1_, entitya0_.entityb_id as entityb_3_0_1_, entitya0_.name as name2_0_1_, entityb1_.id as id1_1_0_, entityb1_.name as name2_1_0_ from entitya entitya0_ left outer join entityb entityb1_ on entitya0_.entityb_id=entityb1_.id where entitya0_.id=?
org.hibernate.SQL: select entityb0_.id as id1_1_0_, entityb0_.name as name2_1_0_ from entityb entityb0_ where entityb0_.id=?
org.hibernate.SQL: select entitya0_.id as id1_0_1_, entitya0_.entityb_id as entityb_3_0_1_, entitya0_.name as name2_0_1_, entityb1_.id as id1_1_0_, entityb1_.name as name2_1_0_ from entitya entitya0_ left outer join entityb entityb1_ on entitya0_.entityb_id=entityb1_.id where entitya0_.id=?
org.hibernate.SQL: insert into entityb (name, id) values (?, ?)
org.hibernate.SQL: insert into entitya (entityb_id, name, id) values (?, ?, ?)
org.hibernate.SQL: insert into entitya (entityb_id, name, id) values (?, ?, ?)

EntityA(name=a-1, entityB=EntityB(name=b-1))
EntityA(name=a-2, entityB=EntityB(name=b-1))

如果我想使用ID-Generator并从entity-creator中移除setId(...),我会在NullPointerException中获得HsqlException

    Caused by: org.hsqldb.HsqlException: java.lang.NullPointerException
    at org.hsqldb.error.Error.error(Unknown Source) ~[hsqldb-2.3.3.jar:2.3.3]
    at org.hsqldb.persist.RowStoreAVL.indexRow(Unknown Source) ~[hsqldb-2.3.3.jar:2.3.3]
    at org.hsqldb.TransactionManager2PL.addInsertAction(Unknown Source) ~[hsqldb-2.3.3.jar:2.3.3]
    at org.hsqldb.Session.addInsertAction(Unknown Source) ~[hsqldb-2.3.3.jar:2.3.3]
    at org.hsqldb.Table.insertSingleRow(Unknown Source) ~[hsqldb-2.3.3.jar:2.3.3]
    at org.hsqldb.StatementDML.insertRowSet(Unknown Source) ~[hsqldb-2.3.3.jar:2.3.3]
    at org.hsqldb.StatementInsert.getResult(Unknown Source) ~[hsqldb-2.3.3.jar:2.3.3]
    at org.hsqldb.StatementDMQL.execute(Unknown Source) ~[hsqldb-2.3.3.jar:2.3.3]
    at org.hsqldb.Session.executeCompiledStatement(Unknown Source) ~[hsqldb-2.3.3.jar:2.3.3]
    at org.hsqldb.Session.execute(Unknown Source) ~[hsqldb-2.3.3.jar:2.3.3]
    ... 87 common frames omitted
Caused by: java.lang.NullPointerException: null
    at org.hsqldb.index.IndexAVLMemory.insert(Unknown Source) ~[hsqldb-2.3.3.jar:2.3.3]
    ... 96 common frames omitted

至少我想要的是我不需要给entityA一个标识符,它应该由它自己生成,我想要至少两个实体A.

1 个答案:

答案 0 :(得分:1)

  

至少我想要的是我不需要给entityA一个标识符,它应该由它自己生成,我想要至少两个实体A.

我相信您不想手动为entityA和entityB分配ID。如果是这样,您可以删除a.setId(1L)a.setId(2L)b.setId(1l)并尝试。

当您使用@GeneratedValue时,它会使用stragegy作为GenerationType.AUTO并为您填充ID。

另一方面,repository.save(...)是一种双重方法,它决定具有相同ID的实体是否已存在。如果具有该id的实体已存在于数据库中,则它会发出update语句,否则会发出insert语句。

在您的情况下,由于您正在使用同一个具有相同id的entityB实例,因此我认为它将它视为现有实体,为第二个entityA发出更新语句而不是创建新实体。

<强> 更新

我已尝试删除对setId(...)EntityA上的EntityB的调用,但导致跟踪错误,因为unique=true出现在{name上1}} EntityB的字段。

Hibernate: insert into entityb (id, name) values (null, ?)
Hibernate: insert into entitya (id, entityb_id, name) values (null, ?, ?)
Hibernate: insert into entityb (id, name) values (null, ?)
2016-09-19 18:11:28.960  WARN 10956 --- [nio-8080-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 23505, SQLState: 23505
2016-09-19 18:11:28.960 ERROR 10956 --- [nio-8080-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper   : Unique index or primary key violation: "UK_Q9VYNGA314JSWU3TEA1LCF3P4_INDEX_C ON PUBLIC.ENTITYB(NAME) VALUES ('b-1', 1)"; SQL statement:
insert into entityb (id, name) values (null, ?) [23505-190]
2016-09-19 18:11:28.990 ERROR 10956 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["UK_Q9VYNGA314JSWU3TEA1LCF3P4_INDEX_C ON PUBLIC.ENTITYB(NAME) VALUES ('b-1', 1)"; SQL statement:
insert into entityb (id, name) values (null, ?) [23505-190]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause

org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: "UK_Q9VYNGA314JSWU3TEA1LCF3P4_INDEX_C ON PUBLIC.ENTITYB(NAME) VALUES ('b-1', 1)"; SQL statement:
insert into entityb (id, name) values (null, ?) [23505-190]

删除unique=true后,导致按预期创建了两个EntityA个实例

Hibernate: insert into entityb (id, name) values (null, ?)
Hibernate: insert into entitya (id, entityb_id, name) values (null, ?, ?)
Hibernate: insert into entityb (id, name) values (null, ?)
Hibernate: insert into entitya (id, entityb_id, name) values (null, ?, ?)
Hibernate: select entitya0_.id as id1_3_, entitya0_.entityb_id as entityb_3_3_, entitya0_.name as name2_3_ from entitya entitya0_
EntityA [id=1, name=a-2, entityB=EntityB [id=1, name=b-1]]
EntityA [id=2, name=a-1, entityB=EntityB [id=2, name=b-1]]

因此,您可能希望删除name的{​​{1}}字段上的唯一约束,并测试并删除调用entityB setId(...)`方法。

仅供参考,我使用了h2数据库进行测试。