views:

84

answers:

4

I am creating a billing system which manages a Ledger for each customer. Each Ledger has a list of LedgerEntry's which records each customer transaction.

As I was testing this, I noticed that if I created a bunch of LedgerEntry's within the same transaction, the @Id value was not in order that the objects were given to em.persist(), unless I did a em.flush() after each entry is created.

Because I rely on the order of the id for the proper behavior of the Ledger (specifically, the current balance is the last LedgerEntry in the list -- enforced by a @OrderBy "id ASC"), this means that I have to flush() a bunch of times.

Is there a way to avoid flushing after each row is created? That is, to have some sort of ordering on how the objects are persisted, without using an @OrderColumn?

A: 

To my knowledge, there is nothing about the ordering of statements and the order of EntityManager method calls in the spec. So I would really not rely on that, even if your provider allows to be configured via some kind of proprietary settings or offers this behavior by default.

But actually, if you need to deal with ordered list, it would IMO make a lot of sense to use a column to maintain the persistent order and to define it using an OrderColumn. You should IMO not rely on the PK for business things.

Pascal Thivent
Even if the PK is used strictly for monotonic ordering? The PK's do not have to be contiguous -- just ensure that they are retrieved in the same order with the latest entry last.
Toybuilder
@Toybuilder In my opinion, the PK is a technical thing, not a business thing, especially since the order is not guaranteed. But that's just my opinion :)
Pascal Thivent
A: 

I'm not totally sure I understand your structure.

You should be able to sort a single business transaction (multiple LedgerEntry rows?) by transaction_timestamp to get the proper order.

But why is the current balance row in the table at all? It could be computed, yes?

If the current balance row has to be there, I'd create a line_item_number column and assign the values sequentially for each business transaction. This will probably make a nice unique key when coupled with the Ledger id. And I'd assign the current balance row a magic value of 999999 so it would always sort last.

A nice side effect is that picking the amount owed from the table is real easy - sum all rows where line_item_number = 999999.


My preference, however, would be to remove the current_balance row and see the problems just go away.


Another option is to put the computer current_balance in the Ledger and not have it as a LedgerEntry row.

Tony Ennis
To answer the specific question of why store the balance as well as the amount of the transaction -- it's because by doing that, you can archvie away older ledger entries.
Toybuilder
BTW, the timestamp doesn't work in this case because JPA writes the ledger entry rows out all at the same time - so all entries get the same timestamp. The only way around that is to flush() to the database after each row is created, which is basically what I am doing.
Toybuilder
A: 

I'm not sure if this is generic JPA or Hibernate-only, but note the difference between Hibernate's persist() and save(). From the docs (emphasis mine):

  • persist() makes a transient instance persistent. However, it does not guarantee that the identifier value will be assigned to the persistent instance immediately, the assignment might happen at flush time.
  • save() does guarantee to return an identifier. If an INSERT has to be executed to get the identifier ( e.g. "identity" generator, not "sequence"), this INSERT happens immediately, no matter if you are inside or outside of a transaction.

So if you use save(), IDs should be generated in the order that you invoke save(). If doing an insert just to get the ID is problematic (for performance etc) you can choose to use another ID generator which doesn't require hitting the database.

Cowan
Well, there is nothing in the question, which is tagged JPA, about Hibernate :)
Pascal Thivent
@Pascal indeed, hence my first sentence. I've only used Hibernate; I know it's an implementation of JPA, but includes a bunch of extensions. It's not clear to me, not having used 'pure' JPA, if `save` is a Hibernate extension or if it's mandated by the JPA spec.
Cowan
JPA doesn't have an equivalent of Hibernate `save`, JPA only defined `persist`. So you'd have to unwrap the `session` "wrapped" by the `EntityManager` to use `save`, assuming Hibernate is used as JPA provider of course. But this someone defeats the idea of using JPA. Anyway, I still believe the OP shouldn't rely on the *PK* :)
Pascal Thivent
Ah ha! I am using an identity generator rather than sequences. I know that in some project's I've worked with in the past, the client can get a sequence number block fromt he db server and consume the sequence numbers without hitting the database for each sequence number.
Toybuilder
A: 

And the ids are generated using what method ? sequence ? table ? user-assigned ? There is no guarantee, as mentioned by other replies, of order of assignment, but if the generation method is not dependent on the datastore then most implementations will set the value at the time of the call to "persist". If the generation method OTOH uses the datastore then you will have to flush to force this datastore contact.

DataNucleus