简体   繁体   中英

How do I get Hibernate/Spring/JPA to auto update the id of a new entity

I have an aggregate that has child entities. When I save the aggregate, the IDs of the aggregate and the child entities don't get updated. Is there a way to have the JPA entity manager automatically update the IDs, or is the only solution to save and get the return aggregate and then find the child entities you just added again? I'm using the Spring CrudRepositories to save all my aggregates.

I'm using:

Spring 4.0.3

Hibernate 4.3.5

JPA 2.1

Here is the basic code that I have.

@MappedSuperclass
public class AbstractEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", nullable = false)
    protected Long id;
}


@Entity
@Table(name = "foo")
public class Foo extends AbstractEntity{
    @OneToMany(cascade=CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "foo")
    private List<Bar> bars;

    public void addBar(Bar bar)
    {
        bar.setBar
        bars.add(bar);
    }
}

@Entity
@Table(name = "bar")
public class Bar extends AbstractEntity{
    @ManyToOne
    @JoinColumn(name = "foo_id", nullable = false)
    private Foo foo;

    protected setFoo(Foo foo){
        this.foo = foo;
    }
}

@Repository
@Transactional(propagation = Propagation.MANDATORY)
interface FooRepository extends CrudRepository<Foo, Long> {
}

@Service
public class DoStuffService()
{
    @AutoInject
    private FooRepository fooRepository;

    public Bar doStuff(long id)
    {
        Foo foo = fooRepository.findOne(id);
        Bar bar = createSomeBar();
        foo.addBar(bar);
        fooRepository.save(foo);
        return bar; // I would like this to have the id filled out from the save of the aggregate rather than having to go to the get the Foo again and go through the collection to find the right bar so I can return it with an ID.
    }
}

@Service
@Transactional
public class SomeRootServiceService()
{
    @AutoInject
    private DoStuffService doStuffService;

    public Bar doRootStuff()
    {
         Bar bar = doStuffService.doStuff(someId);
         if (bar.getId() == null){
             //Should never get here but I do.
         }
    }
}

EDIT : Adding some information to clarify what our final goal is, the id issue is just a symptom of that.

What we're really trying to do is to take an entity that is created from an aggregate and pass it to something else that links to the newly created entity all inside of the same database transaction, and since the entity isn't yet "saved" hibernate throws a transient entity error.

You can use the following code:

@Id
@Column(name = "id") //Don't need nullable = false as primary keys can never be null
@GeneratedValue(generator = "generatorName")
@SequenceGenerator(name = "generatorName", sequenceName = "SEQ_NAME")
protected Long id;

You can either create the sequence yourself in your database or if Hibernate is generating your schema for you it will do it.

Hibernate will use these annotations to automatically get the next value of the sequence from the database when creating a new object without you having to do a thing. Its incredibly convenient. If you have your cascades set up correctly, you should only have to save the parent object and then the children will get created and ID's assigned automatically.

For what its worth, having a mapped abstract superclass just to encapsulate out ID's doesn't make sense to me. An object's ID is intrinsic to its individual state and each object should have a unique sequence to generate ID's from. Why do you need both objects to use the same ID source. Each object should have a unique ID for its own table.

You only encapsulate functionality in a super class if all subclasses share that functionality, and while your objects both need ID's these ID's are not related to each other. So you can't say that Foo_ID is related to Bar_ID, they should be totally separate from each other.

On another unrelated note, why are you not using @AutoInject instead of @Autowired in Spring?

On a third unrelated note, why are your DAO's annotated as @Transactional. The Service layer is where all transaction management and object manipulation should be taking place. If you need your DAO annotated @Transactional you have a problem. Also you shouldn't need that propogation modifier. Hibernate does an excellent job of keeping transactions from interfering with each other.

Persisting an entity doesn't immediately generate the SQL query that generates the ID and saves the entity. These queries are actually delayed in order to batch them, and avoid them in case of a rollback, to gain performance.

At the very least the queries will thus actually be executed before the transaction is committed. But you're trying to access the ID before the transaction commits. And that's why, I think, you get null. You could confirm this by enabling SQL logging (that should always be the case during development, because it's extremely helpful), and executing your code line by line using your debugger.

So if you need access to the generated ID inside the transaction, make sure to flush the session (or the entity manager if using JPA) before getting the ID. That will execute all the pending queries, and generate your ID.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM