TomR
TomR

Reputation: 3056

Multiple Spring Data JPA repositories in single transaction

I am trying to map the following code: * Order contains many OrderItems * Article can be referenced by (contains) many OrderItems

In one transaction I need to create Order, several Articles and OrderItems with the new articles. All is clear when there is straigth path - Order contains OrderItems and so on - then I can call

orderRepository.save(order);
orderRepository.flush();

New order entity with the OrderItems entities is saved. The problem begins with trying to save Article entities as well. Apparently more work is required, because I am getting the error message;

org.springframework.dao.InvalidDataAccessApiUsageException: 
org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing

How to organize such work in one transaction? Do multiple repositories can work under single transaction and if so - whose flush() completes the transaction. Are there necessary multiple flushe's (e.g. one for article entities and another for order) and if so - how to rollback all of them, if necessary?

Apparently it is not possible to mix repositories with the simple EntityManager.getTransaction().{begin(), commit()}, code because then the error message is:

java.lang.IllegalStateException: Cannot obtain local EntityTransaction from a transaction-synchronized EntityManager

What is the best practice in case where one entity (OrderItem) is owned by two entities (Order and Article) and all three entities should be created in one transaction?

Partial code added. completeOrder is the test procedure, where 5 items and 5 new articles are created for new order. This procedure gives exception "references and unsaved transient instance". I am using composite key for Article entity, but this should not be an issue, I am trying now the same scenario with Article entity that has usual single-field key.

@Entity
public class Order extends FrameworkEntity {
    ...
    @OneToMany(mappedBy="order", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private List<OrderItem> orderItems = new ArrayList<OrderItem>();
}


@Entity
public class OrderItem extends FrameworkEntity {
    ...
    @ManyToOne
    @JoinColumn(name="fk_order")
    private Order order;

    @ManyToOne
    @JoinColumns({
        @JoinColumn(name="fk_date"),
        @JoinColumn(name="fk_number")
    })
    private Article article;
}

@Entity
public class Article extends FrameworkEntity {
    ...
    @Id
    protected PkArticle pkArticle;

    @OneToMany(mappedBy="article", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private List<OrderItem> orderItems = new ArrayList<OrderItem>();
}

@Embeddable
public class PkArticle extends FrameworkCompositeKey {

    @Column(name = "article_number")
    private Long articleNumber;

    @Temporal(TemporalType.DATE) 
    @Column(name = "article_date")
    private Date articleDate;
    ...
}


public class OrderService {

    public void completeOrder() {

        Order entity = new Order();
        for (int i=0; i<5; i++) {
            Date articleDate;
            Long articleNumber;

            Calendar cal = Calendar.getInstance();
            cal.setTimeInMillis(0);
            cal.set(2015, 1, 31, 0, 0, 0);
            articleDate = cal.getTime(); 
            articleNumber = (new Random()).nextLong();

            Article article = new Article(articleNumber, articleDate);

            //entityManager.getTransaction().begin();
            //entityManager.persist(space);
            //entityManager.getTransaction().commit();

            OrderItem item = new OrderItem();

            item.setArticle(article);
            article.getOrderItems().add(item);

            item.setOrder(entity);
            entity.getOrderItems().add(item);
        }
        entity=(Order) repository.save(entity);
        repository.flush();
    }
]

Upvotes: 2

Views: 3924

Answers (1)

manish
manish

Reputation: 20135

Do you have code like this?

@Entity
public class OrderItem {
  @ManyToOne
  private Article article;
}

If yes, change this to:

@Entity
public class OrderItem {
  @ManyToOne(cascade = CascadeType.PERSIST)
  private Article article;
}

This will ensure that when a new OrderItem with a yet-to-be-saved Article is persisted, the Article will be saved as well.

Upvotes: 5

Related Questions