Jpa query pulling from cache instead of database? - java

I am hitting an issue with my Hibernate backed Jpa queries returning data that is not up to date. I assume it is an issue with pulling data from the cache instead of the database itself.
For example, I will change and persist an object on one page and then go back to the previous page, which lists rows of the database, and it will show the objects as they existed prior to the change. I can see my query fire from my DAO in my logs and I can go into the database and see that the changes have been persisted, but JPA is not pulling up to date data from the database when I move on to the next page.
I believe there might be some kind of session caching at work, as I will not see an updated database view when I load the page up in another web browser.
How do I fix this issue?
EDIT: I've done some follow up testing, including logging on my controller to make sure my MVC framework (Spring MVC) isn't caching anything. It isn't, even on the controller level it sees out of date database information.
Here is the mapping snippet from my ORM entity file;
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = true)
#Column(name = "ID", nullable = false)
private Integer id;
#Basic(optional = false)
#Column(name = "Name", nullable = false, length = 100)
private String name;
#Basic(optional = false)
#Column(name = "DayOffset", nullable = false)
private int dayOffset;
#Basic(optional = false)
#Column(name = "StartTime", nullable = false, length = 5)
private String startTime;
#Basic(optional = false)
#Column(name = "Enabled", nullable = false)
private boolean enabled;
#Basic(optional = false)
#Column(name = "LastTouched", insertable = false, updatable = false, nullable =
false)
#Temporal(TemporalType.TIMESTAMP)
private Date lastTouched;
#Column(name = "TouchedBy", length = 50)
private String touchedBy;
#JoinTable(name = "ReconciliationSearchRule",
joinColumns = {#JoinColumn(name = "ReconciliationId",
referencedColumnName = "ID", nullable = false)},
inverseJoinColumns = {#JoinColumn(name = "SearchRuleId",
referencedColumnName = "ID", nullable = false)})
#ManyToMany(fetch = FetchType.LAZY)
private Collection<SearchRule> searchRuleCollection;
#JoinColumn(name = "ServerId", referencedColumnName = "ID", nullable = false)
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private Server server;
#OneToMany(mappedBy = "reconciliation")
private Collection<Report> reportCollection;

I figured out what was going on. My DAO is being injected as a prototype (aka non-singleton) so the backing EntityManager was being created for each use of the DAO. Database changes external to the EntityManager are not registered by queries to that particular EntityManager.
Of course setting the DAO to a singleton causes other issues with the multi-threaded part of my application, but that is a whole other issue.

There are a couple ways of going about this:
Set an eviction strategy using the #DataCache(timeout = 1000) on the entity in question and set it to something reasonable for the object type you are using.
Call evict(...) on your entity or class. The implementation details of this are specific based upon what provider (e.g. Hibernate) you are using.

For example, I will change and persist an object on one page and then go back to the previous page, which lists rows of the database, and it will show the objects as they existed prior to the change.
Second level cache management should be transparent, you don't have to evict() an entity manually after any update, the 2nd level cache gets invalidated for the target table "automatically" by Hibernate.
So, my question is: what do you mean by "going back to the previous page"? Do you mean using the navigator "Back" button? Did you reload the page? Can you clarify this?
Also, can you post your mapping file or annotated entity?

Related

best practice for implement ManyToOne

I always use the following method to implement ManyToOne in class :
#Column(name = "buyer_id")
private Long buyerId;
#ManyToOne
#JoinColumn(name = "buyer_id", insertable = false, updatable = false)
UserGroup buyer;
However, I have a question whether it is better to use the following code:
#ManyToOne
#JoinColumn(name = "buyer_id", insertable = false, updatable = false)
UserGroup buyer;
or not?
In the first case, I always set the id value obtained in buyerId for saving after saving the UserGroup, but in the second case, I put the userGroup model completely (after save) in my entity then save entity.
I'm not sure if the method I use is the best.
I searched the internet but could not find a technical reason that the latter is not appropriate.

Why MERGE operation works when saving parent with no primary key which has children with primary key?

I have following classes in bidirectional many to many relationship.
#Table(name = "message")
#Entity
public class Message {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "message_id", unique = true, nullable = false)
private int id;
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.MERGE)
#JoinTable(name = "tags_messages",
joinColumns = #JoinColumn(name = "message_id", referencedColumnName = "message_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id", referencedColumnName = "tag_id"))
private Set<Tag> tags=new HashSet<>();
and
#Table
#Entity(name = "tag")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "tag_id", unique = true, nullable = false)
private int id;
#Column(name = "name", unique = false, nullable = false)
private String name;
#JsonIgnore
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.MERGE)
private Set<Message> messages;
When trying to save new Message, I got exception saying: "detached entity to persist...Tag". I got it to work by setting CascadeType.MERGE, but I don't understand why it is working. It would be great if someone can explain me why :)
Steps I did which lead to exception:
In db I already had two Tags objects and no Messages. I also had connecting empty table messages_tags
On frontend (Android) I create new Message object (without id), add one Tag (entire object, pulled from db, with id) to Message.
Send new Message to backend (using Retrofit). Hit my controller function, then service function in which I tried to save new Message with accompanying child Tags. At first, my cascading type annotation on both side, was like this:
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
I thought, since I have one part of relationship covered, that I need to cover other one as well. So I did this:
newMessage.getTags().forEach(t -> t.getMessages().add(newMessage));
messageRepository.save(newMessage) //Bum! exception
I commented out that line for setting other part of relationship, set MERGE as cascading type and save simply worked. WHY? Are there any other consequences I may experience while doing other CRUD operations on any of these entities?
When you add a new Tag to the Message on the frontend, you have a different persistent context from the one used on backend. That's why the tag entity is seen as detached (it has a PK but it is not in the backend's persistent context). Since you did not specify a cascade type for JPA to know that to do with Tag instance, the persist of the Message instance fails.
Once you specify the cascade type MERGE, the Tag instance is merged into the backend's persistent context and the save succeeds.
You can avoid the using of MERGE cascade by saving first the Tag instance.

how to address a database column multiple times using hibernate

I have a project to access a database via REST, in which an old database has to be addressed in a modern way using javax.persistence annotations and JPA.
One of my classes fields look like this:
#Column(name = "properties_id", nullable = false)
private int propertiesId;
#Id
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "properties_id", referencedColumnName = "dbid", insertable = false, updatable = false)
private PropertyList propertyList;
#Id
#Column(name = "propertiesIdx", nullable = false)
private int propertiesIdx;
As you can see in the fields, the column properties_id is used as part of the primary key and as a foreign key. Unfortunately, there is no possibility to change the database layout.
In the application, I need both fields: one for joining the parent-table and another for addressing the property directly.
But in the way it is currently implemented, it doesn't work.
Does somebody have an idea how to address this problem?
For further details of the problem please feel free to visit the git-repo:
https://github.com/SerNet/verinice-rest-service/tree/import

Netbeans - Glassfish server deployment error due to composite primary key

I'm currently having an issue with a BUILD FAILED error when deploying my Netbeans project to Glassfish server. I get the following error from the log:
Exception Description: The #JoinColumns on the annotated element [field instrument] from the entity class [class entity.InstrumentExtRef] is incomplete. When the source entity class uses a composite primary key, a #JoinColumn must be specified for each join column using the #JoinColumns. Both the name and the referencedColumnName elements must be specified in each such #JoinColumn.. Please see server.log for more details.
The table that's having the issue is instrument_ext_ref and has a composite primary key of the following fields: instrument_id and instrument_code_type.
The entity class that seems to have the problem - InstrumentExtRef - has the following declarations and annotations.
#EmbeddedId
protected InstrumentExtRefPK instrumentExtRefPK;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 16)
#Column(name = "reference")
private String reference;
#JoinColumn(name = "instrument_code_type", referencedColumnName = "code_type", insertable = false, updatable = false)
#ManyToOne(optional = false)
private InstrumentCodeType instrumentCodeType1;
#JoinColumn(name = "instrument_id", referencedColumnName = "id", insertable = false, updatable = false)
#ManyToOne(optional = false)
private Instrument instrument;
And the entity class containing the #EmbeddedId details (InstrumentExtRefPK) looks like this:
#Embeddable
public class InstrumentExtRefPK implements Serializable {
#Basic(optional = false)
#NotNull
#Column(name = "instrument_id")
private int instrumentId;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 10)
#Column(name = "instrument_code_type")
private String instrumentCodeType;
Class InstrumentCodeType refers to table instrument_code_type which has a primary key code_type (the referenced column). Class Instrument refers to table instrument which itself has a composite primary key consisting of columns "id" (the referenced column) and column "exchange_exchange_code".
It seems obvious from the error message that the composite primary key in the InstrumentExtRef entity is the issue but based on my research of other similar (but not quite the same) issues I think my entity classes look OK. But I'm relatively new to this and may be missing something.
After doing some more research on creating/uses of composite keys in entity classes I saw a subtle point in the error message I was receiving.
"...must be specified for each join column using the #JoinColumn*s*". I wasn't using the #JoinColumns annotation. Or at least Netbeans didn't create the entity class that way. Probably because each join column is from a different source entity/table.
After correcting my entity classes as follows the project was able to be deployed successfully.
#JoinColumns({
#JoinColumn(name = "instrument_code_type", referencedColumnName = "code_type", insertable = false, updatable = false),
#JoinColumn(name = "instrument_id", referencedColumnName = "id", insertable = false, updatable = false)})
private InstrumentCodeType instrumentCodeType1;
private Instrument instrument;
It was here that I started to see what the problem might be:
https://access.redhat.com/site/documentation/en-US/JBoss_Enterprise_Application_Platform/5/html/Hibernate_Annotations_Reference_Guide/ch02s02s06.html
I suspect that my next problem will be something to do with the fact that the composite primary key is made up of columns from two different entities - instrument and instrumentCodeType. But for now the above change got me past the build problem and if that next problem arises I'll deal with it separately.

JPA merge results in duplicate entry of foreign entity

QUESTIONS:
Does anyone know how to merge without having EntityManager trying to re-insert the foreign entity?
SCENARIO:
Just to set up a scenario that closely matches my case: I have two entities
#Entity
#Table(name = "login", catalog = "friends", uniqueConstraints =
#UniqueConstraint(columnNames = "username"))
public class Login implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#Column(name = "username", unique = true, nullable = false, length = 50)
private String username;
#Column(name = "password", nullable = false, length = 250)
private String password;
}
#Entity
#Table(name = "friendshiptype", catalog = "friends")
public class FriendshipType implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "username")
private Login login;
#Column(name = "type", unique = true, length = 32)
private String type;
...//other fields go here
}
Both the Login entity and the FriendshipType entity are persisted to the database separately. Then, later, I need to merge a Login row with a FriendshipType row. When I call entityManager.merge(friendship), it tries to insert a new Login which of course results in the following error
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'myUserName1350319637687' for key 'username'
Error Code: 1062
Call: INSERT INTO friends.login (password, username) VALUES (?, ?)
My question, again, is how do I merge two objects without having enityManager trying to reinsert the foreign object?
Here is how I solve the problem. I finally figure the reason the merge is not resolving is because the login.id is auto generated by JPA. So since I really don't need an auto-generated id field, I remove it from the schema and use username as the #id field:
#Entity
#Table(name = "login", catalog = "friends", uniqueConstraints =
#UniqueConstraint(columnNames = "username"))
public class Login implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "username", unique = true, nullable = false, length = 50)
private String username;
#Column(name = "password", nullable = false, length = 250)
private String password;
}
Another solution that occurred to me, which I didn't implement but may help someone else, should they need to have an auto-generated id field.
Instead of creating an instance of Login for the merger, get the instance from the database. What I mean is, instead of
Login login = new Login(); login.setUsername(username); login.setPassword(password);
Do rather
Login login = loginDao.getByUsername(username);
That way, a new id field is not generated making the entity seem different.
Thanks and up-votes to everyone for helping, especially to #mijer for being so patient.
You can make your #JoinColumn non updatable:
#JoinColumn(name = "login_id", updatable = false) // or
#JoinColumn(name = "username", referencedColumnName = "username", updatable= false)
Or try to refresh / fetch your Login entity again before merging the FriendshipType:
// either this
entityManager.refresh(friendship.getLogin());
// or this
final Login login = entityManager
.getReference(Login.class, friendship.getLogin().getId());
friendship.setLogin(login);
// and then
entityManager.merge(friendship);
But, as other suggested I belive that FriendshipType would be better represented by a #ManyToOne relationship or maybe by a Embeddable or ElementCollection
Update
Yet another option is to change the owning side:
public class Login implements java.io.Serializable {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "friendshiptype_id")
private FriendshipType friendshipType;
// Other stuff
}
public class FriendshipType implements java.io.Serializable {
#OneToOne(fetch=FetchType.LAZY, mappedBy="friendshipType")
private Login login;
// Other stuff
}
This will affect your data model (login table will have a friendshiptype_id column instead of the other way around), but will prevent the errors that you are getting, since relationships are always maintained by the owning side.
Have you tried cascade=MERGE? I.e.
#OneToOne(fetch = FetchType.LAZY, cascade=CascadeType.MERGE)
#JoinColumn(name = "username")
private Login login;
UPDATE
Another possible option is to use #ManyToOne (it's save as the association is unique)
#ManyToOne(fetch = FetchType.LAZY, cascade=CascadeType.MERGE)
#JoinColumn(name = "username")
private Login login;
You can do it with your original #Id setup. i.e.
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
You can, but you don't need to change to:
#Id
#Column(name = "username", unique = true, nullable = false, length = 50)
private String username;
The trick is you must start by loading from the DB, via em.find(...) or em.createQuery(...). Then the id is guaranteed to be populated with the right value from the DB.
Then you can detach the entity by ending a transaction (for a transaction-scoped entity manager in a session bean), or by calling em.detach(ent) or em.clear(), or by serialising the entity and passing it over the network.
Then you can update the entity, all the while, keeping the original id value.
Then you can call em.merge(ent) and you will still have the correct id. However, I believe the entity must already pre-exist in the persistent context of the entity manager at this instant, otherwise it will think that you have a new entity (with manually populated id), and try to INSERT on transaction flush/commit.
So the second trick is to ensure the entity is loaded at the point of the merge (via em.find(...) or em.query(...) again, if you have a new persistent context and not the original).
:-)

Categories