I am trying to audit the action that the user performed that resulted in changes to corresponding tables. For example if a user were to transfer money between 2 accounts this would generate the following sequence of events:
Insert transfer amount into Transfer table
Subtract transfer amount from balance in Balance Table for Account 1.
Add transfer amount to balance in Balance Table for Account 2.
The parent audit message for all tables would be: "User generated transfer for amount XXX"
This is achieved with the following schema:
schema
alt text http://img48.imageshack.us/img48/7460/auditloggingiv6.png
The question is how do I represent this in hibernate?
I have created the following:
In Balance and Transfer's mapping files
<set name="auditRecords" table="TransferAuditRecord" inverse="false" cascade="save-update">
<key>
<column name="AuditRecordID" not-null="true" />
</key>
<one-to-many class="audit.AuditRecord"/>
</set>
Transfer and Balance classes then implement IAuditable which has methods
public void setAuditRecords(Set<AuditRecord> auditRecord);
public Set<AuditRecord> getAuditRecords();
In AuditRecord's mapping file I have:
<many-to-one name="parentAuditRecord" lazy="false"
column="parent_id"
class="audit.AuditRecord"
cascade="all" />
Then in Logging class using AOP and Hibernate Interceptors I have:
AuditRecord auditRecord = new AuditRecord();
auditRecord.setUser(userDAO.findById(
org.springframework.security.context.SecurityContextHolder.getContext()
.getAuthentication().getName()));
auditRecord.setParentAuditRecord(getCurrentActiveServiceRecord());
auditable.getAuditRecords().add(auditRecord);
Then in the Service Class I call the following method, enclosed in a transaction:
save(balance1);
save(balance2);
transfer.setPassed(true);
update(transfer);
The parentAuditRecord is created using AOP with a thread safe stack, and the AuditRecordType_id is set using annotations on the method.
I left out the "passed" column on the transfer table. Previously I call save(transfer) to insert the transfer amount into the Transfer table with passed set to false. (This action is also audited).
My requirements are slightly more complicated than the example above :P
So the sequence of events for the above should be:
Update Transfer Table
Insert into AuditRecord (Parent)
Insert into AuditRecord (Child)
Insert into TransferAuditRecord
Insert into Balance Table
Insert into AuditRecord (Child)
Insert into BalanceAuditRecord
Insert into Balance Table
Insert into AuditRecord (Child)
Insert into BalanceAuditRecord
However the cascade options defined above fail at the update statement. Hibernate refuses to insert a record into the many-to-many table (even if unsaved-value="any" on the AuditRecord Mapping). I always want to insert rows into the many-to-many tables so potentially one Transfer has many Audit Records marking the previous events. However, the latest event determines the message the user wants to see. Hibernate either tries to update the many-to-many table and previous AuditRecord entries or it simply refuses to insert into AuditRecord and TransferAuditRecord, throwing a TransientObjectException.
The Audit Message is retrieved something like this:
msg=... + ((AuditRecord) balance.getAuditRecords().toArray()[getAuditRecords().size()-1])
.getParentAuditRecord().getAuditRecordType().getDescription() + ...;
The message should say something like this:
"Username set transfer to passed at 12:00 11-Oct-2008"
EDIT I decided to go with explicitly mapping the many-to-many table (with an associated interface), and then in afterTransactionCompletion, calling save on the parent audit record (which cascades the save to the child audit records) then explicitly saving the interface on all child mapping tables. This isn't a true audit history, rather a non-invasive method of recording user action. I will look into Envers if I need more complete audit history at a later point.
Seems like the relationship between parentAuditRecord and transferauditrecord and balance auditrecord shouldn't be one to many. When I read what you typed I'm seeing it as a table per subclass usage of that audit hierarchy which is a one-to-one relationship.
http://www.hibernate.org/hib_docs/reference/en/html/inheritance.html
You may also want to check out JBoss's Envers project.
At the design level, it seems like a insert only db design would work marvels here.
If you want to keep it the way it is right now (which I'm sure you do), you could look into Hibernate listeners/interceptors/events (well defined in the doc: http://www.hibernate.org/hib_docs/v3/reference/en-US/html_single/)
Else, I just looked into JBoss Envers and it also seems pretty useful.
Related
I have configured hibernate to batch insert/update entities via the following properties:
app.db.props.hibernate.jdbc.batch_size=50
app.db.props.hibernate.batch_versioned_data=true
app.db.props.hibernate.order_inserts=true
app.db.props.hibernate.order_updates=true
(Ignore the app.db.props prefix, it is removed by Spring) I can confirm that the properties are making it to hibernate because simple batches work as expected, confirmed by logging via the datasource directly. The proxy below produces logging to show that batches are happening.
ProxyDataSourceBuilder.create(dataSource)
.asJson().countQuery()
.logQueryToSysOut().build();
Logs (notice batchSize)...
{"name":"", "connection":121, "time":1, "success":true, "type":"Prepared", "batch":true, "querySize":1, "batchSize":18, "query":["update odm.status set first_timestamp=?, last_timestamp=?, removed=?, conformant=?, event_id=?, form_id=?, frozen=?, group_id=?, item_id=?, locked=?, study_id=?, subject_id=?, verified=? where id=?"], "params":[...]}
However when inserting a more complex object model, involving a hierarchy of 1-* relationships, hibernate is not ordering inserts (and thus not batching). With a model like EntityA -> EntityB -> EntityC, hibernate is inserting each parent and child and then iterating, rather than batching each entity class.
I.e. what I see is multiple inserts for each type...
insert EntityA...
insert EntityB...
insert EntityC...
insert EntityA...
insert EntityB...
insert EntityC...
repeat...
But what I would expect is a single iteration, using a bulk insert, for each type.
It seems like the cascading relationship is preventing the ordering of inserts (and the bulk insert), but I can't figure out why. Hibernate should be capable of understanding that all instances of EntityA can be inserted and once, then EntityB, and so on.
I am following the NetBeans E-commerce tutorial - on the 9th Tutorial which is about Integrating Transnational Business Logic
Where they show how create the OrderManager class with placeOrder() Method - and the method is transactional which involves three tables - first customer, then customer_order and finally orderedItem using em.persist().
but the em.persist() method is not persisting for customer - but it will persist for customer if I manually supplied the customer id manually into the code (hard code).
but for the customer_order it will not persist even after persisting the customer by manual id provision and using em.flush();
I googled and couldn't seem to find way out. P.S. The Entity class is generated with Netbeans wizard - and the id generation strategy IDENTITY
The em.persist() was not persisting because the #NotNull annotation on the id fields was not allowing null - as I am working on Netbeans.
so removing those #NotNull or commenting them out on the entity class gets the job done.
infact I learned this fact from the last post of the following link.
Hibernate Auto Increment ID
I have a couple of objects that are mapped to tables in a database using Hibernate, BatchTransaction and Transaction. BatchTransaction's table (batch_transactions) has a foreign key reference to transactions, named transaction_id.
In the past I have used a batch runner that used internal calls to run the batch transactions and complete the reference from BatchTransaction to Transaction once the transaction is complete. After a Transaction has been inserted, I just call batchTransaction.setTransaction(txn), so I have a #ManyToOne mapping from BatchTransaction to Transaction.
I am changing the batch runner so that it executes its transactions through a Web service. The ID of the newly inserted Transaction will be returned by the service and I'll want to update transaction_id in BatchTransaction directly (rather than using the setter for the Transaction field on BatchTransaction, which would require me to load the newly inserted item unnecessarily).
It seems like the most logical way to do it is to use SQL rather than Hibernate, but I was wondering if there's a more elegant approach. Any ideas?
Here's the basic mapping.
BatchQuery.java
#Entity
#Table(name = "batch_queries")
public class BatchQuery
{
#ManyToOne
#JoinColumn(name = "query_id")
public Query getQuery()
{
return mQuery;
}
}
Query.java
#Entity
#Table(name = "queries")
public class Query
{
}
The idea is to update the query_id column in batch_queries without setting the "query" property on a BatchQuery object.
Using a direct SQL update, or an HQL update, is certainly feasible.
Not seeing the full problem, it looks to me like you might be making a modification to your domain that's worth documenting in your domain. You may be moving to having a BatchTransaction that has as a member just the TransactionId and not the full transaction.
If in other activities, the BatchTransaction will still be needing to hydrate that Transaction, I'd consider adding a separate mapping for the TransactionId, and having that be the managing mapping (make the Transaction association update and insert false).
If BatchTransaction will no longer be concerned with the full Transaction, just remove that association after adding a the TransactionId field.
As you have writeen, we can use SQL to achieve solution for above problem. But i will suggest not to update the primary keys via SQL.
Now, as you are changing the key, which means you are creating alltogether a new object, for this, you can first delete the existing object, with the previous key, and then try to insert a new object with the updated key(in your case transaction_id)
Say I have domain objects corresponding to Posts and Users. Nevertheless, I have corresponding database tables containing information relevant to "posts" and "users".
Currently I have properly set up the mapping in Hibernate so that I can pull the info from the "posts" table and save it as a Post object. However, I then added to my Posts domain object, a reference to a User object (so that each post can have a corresponding User object).
In my database structure, the "posts" table has a user_id column which is a foreign key into the "users" table.
My question is: when querying the "posts" table in my DAO, do I have to join on the "users" table and then somehow cast the returned user data into a User object? Or can I simply leave my query as it is (i.e. just querying the "posts" table), and somehow add a hibernate mapping so that the User attribute in the Posts table gets populated? (I guess I am wondering if Hibernate can automatically generate the join for me if I set up the mapping properly - examples would be great too!)
Thanks, and I hope I was clear enough.
Update: Just to clarify, here are some code snippets:
My Post Object:
public class Posts {
private String title;
...
private User user;
//getters and setters here
}
My Post table columns:
post_id (primary key)
title
...
user_id (foreign key into User table)
My mapping (without taking into account the User attribute) currently looks like this:
<class name="com...Post" table="post">
<id name="pId" column="post_id" />
<property name="title" column="title" type="java.lang.String" />
...
<!-- Need to add mapping here to join with user table?? -->
</class>
So basically, my DAO currently fetches a Post object without the private User user attribute (since I just added this). My question was how do I populate that attribute in the Post object (so that the fetched Post object also contains a User object)?
Sorry if the current posts already answered this...they were just slightly confusing to me..
If I understand your question correctly, I believe you're looking for the many-to-one mapping (many Posts to one User). Add the following to your mapping for the Post object:
<many-to-one name="user" class="User" column="user_id" lazy="false" />
Update: Well, you got a confusing answer first because you asked a confusing question... The answer to your renewed question is indeed to define a many to one mapping for your Post class (as others have already mentioned). Now, if you want to fetch the whole stuff with a single join query, you write:
<many-to-one name="user" class="User" column="user_id" fetch="join" />
Original post:
By default, Hibernate fetches lazily. In fact, you need to touch the lazy property only if you want eager fetching. Rough example for the behaviour of the default lazy fetch plan:
Post post = (Post) session.load(Post.class, new Long(123));
// at this point, post refers to a proxy object created by Hibernate
// in the background - no post or user data has been loaded from DB
post.getId();
// post still refers to the proxy object
User user = post.getUser();
// post is now loaded, but user not - it refers to a proxy object
String name = user.getName(); // Now the user data is loaded from DB
So if you are happy with multiple queries, you don't need to do anything special. OTOH if you want to fetch all post and user data in a join query, you need to set the attribute fetch="join" in your mapping for theuser` property.
You can do that by using the lazy property which is not activated by default.
See some examples here http://www.javalobby.org/java/forums/t20533.html
I am having a querying issue in Hibernate. I have a table, 'test', with existing data. I have a requirement where I can not modify the schema of the test table, so I created another table, 'testExtension', whose primary key is a foreign key to the primary key of Test. Data in testExtension is a subset of the data in test. i.e. There will be less rows in 'testExtension' than in 'test'.
I have defined this relationship in a configuration file as follows:
<class name="Test" table="test">
<id name="testId" column="test_id">
<generator class="sequence">
<param name="sequence">test_id_seq</param>
</generator>
</id>
<property name="name"/>
<joined-subclass name="TestExtension" table="testExtension">
<key column="test_id"/>
<property name="summary" />
<property name="homepage"/>
</joined-subclass>
With this setup, I am able to create a TestExtension object in my Java program, populate it with data, 'save' it via Hibernate, and commit the transaction. And it correctly saves data in both Test and TestExtension.
My problem is occurring when I am trying to query data from these tables. Right now if I query for a particular test_id using the TestExtension.class to QBE, it will only return a row if that id exists in both Test and TestExtension. If I use the Test.class to QBE, it will return the row but I will not have access to any of the data stored in TestExtension.
My question is: how can I query these tables so that the results are based off a 'left outer join' of both Test and TestExtension? Any solution is appreciated, whether it's query by example, HQL, or something else (though preferably not raw SQL).
Thanks!
HQL is probably the easiest way to do this. Docs are here:
http://docs.jboss.org/hibernate/stable/core/reference/en/html/queryhql-joins.html
Sounds like what you might want to do is remap your relationships so that Test and TestExtension use a one-to-one relationship instead of inheritance. Then you can query for Test and TestExtension using a left outer join across the one-to-one.
If you use HQL to write a query for the Test class, it should do what you want. I assume QBE is effectively adding the class of your example entity as one of the query parameters.
So sth like:
from Test t where t.property = :value
should return either Test or TestExtension entities. Note that (at least with the versions of Hibernate I've used). In this case, Hibernate should immediately give you back the actual entities rather than a proxy too--- be aware that TestExtension entities can sometimes be returned as plain Test lazy-loading proxies.