I work with hibernate and try to optimize loading foreign entities annotated with
#ManyToOne(fetch = FetchType.LAZY)
I don't want to retrieve foreign entity during hibernate query and use LAZY fetch type.
Later(after session already closed), I want to get that foreign entity but use tool that different from hibernate (another cached DAO (GuavaCache) that already stores foreign entity).
Of couse, immediately I have got an LazyInitializationException.
I can't replace #ManyToOne annotation with #Transient because of toooooo much legacy HQL code witch does not works after deleting #MabyToOne.
Somewere somebody adviced to make getter method final and do not access to entity field straightly, but just use getter. Here is an example :
private int foreignId;
#Basic
#Column(name = "foregn_id")
public int getForeignId() { return foreignId;}
public void setForeignId(int id) { this.foreignId = id; }
// private DBForeignEntity foreignEntity; no more sense to have this field
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "foregn_id", referencedColumnName = "id", nullable = false, insertable = false, updatable = false)
public final DBForeignEntity getForeign() {
// return foreignEntity; deprecated usage! Hibernate will create proxy object and throw LazyInitException after session was closed
return getFromCache(getForeignId());
}
public void setForeign(DBForeignEntity foreignEntity) {
// this.foreignEntity = foreignEntity; no more sence for having setter at all
}
this ugly solution exludes any abilities to persist nested entities, because of no setter for foreign entity anymore!
Is there another solution to deprecate Hibernate to create a proxy object for my entity?
How to avoid LazyInitializationException if session was closed?
Is there any bad consequences of non-proxing in this case?
In current situation hibernate proxies only child (foreignEntity) entity, and do not proxy current (parent) entity. Thus, there is no problem to check the instance of foreign entity and replace it with custom loaded :
private int foreignId;
#Basic
#Column(name = "foregn_id")
public int getForeignId() { return foreignId;}
public void setForeignId(int id) { this.foreignId = id; }
private DBForeignEntity foreignEntity;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "foregn_id", referencedColumnName = "id", nullable = false, insertable = false, updatable = false)
public final DBForeignEntity getForeignEntity() {
if (foreignEntity instanceof HibernateProxy) {
foreignEntity = getFromCache(foreignId);
}
return foreignEntity;
}
public void setForeign(DBForeignEntity foreignEntity) {
this.foreignEntity = foreignEntity;
}
Further, there is no LazyInitException during invoking
DBParentEntity.getForeignEntity()
Related
My goal is to be able to insert the below object with the user passing in the relevant data and the id of the foreign key, and return back to the user a full object that contains the full foreign key object as well, not just the foreign key id.
#Data
#Entity
#EqualsAndHashCode(callSuper = false)
#Table(name = MY_OBJECT_TABLE)
public class MyObject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "MY_OBJECT_ID", unique = true, nullable = false)
private Integer myObjectId;
#ManyToOne(targetEntity = ForeignObject.class, fetch = FetchType.EAGER)
#JoinColumn(name = "FOREIGN_OBJECT_ID", referencedColumnName = "FOREIGN_OBJECT_ID", nullable = false, insertable = false, updatable = false)
private ForeignObject foreignObject;
#Column(name = "FOREIGN_OBJECT_ID")
private Integer foreignObjectId;
#Column(name = "RANDOM_FIELD", nullable = false)
#NotNull
private Boolean randomField;
}
I was trying with the above and it inserts but it only returns the foreignObjectId on insert and not the entire foreign object.
I tried the below to get it to work but no luck.
#Transactional
public MyObject create(MyObject myObject) {
MyObject createdMyObject = this.myObjectRepository.save(myObject);
return createdMyObject;
}
and also tried
#Transactional
public MyObject create(MyObject myObject) {
MyObject createdMyObject = this.myObjectRepository.save(myObject);
return this.myObjectRepository.findById(createdMyObject.getMyObjectId());
}
I'm not sure if there is something in my domain object I need to change or if I need to change my create method in some way.
Current output is:
{
"myObjectId": 1,
"foreignObject": null,
"foreignObjectId": 3,
"randomField": true
}
Expected output is:
{
"myObjectId": 1,
"foreignObject": {
"foreignObjectId": 3,
},
"foreignObjectId": 3, // I don't care if this field stays here or not
"randomField": true
}
The problem is the following where you are corrupting your domain model to try and make it fit some front-end concern:
#ManyToOne(targetEntity = ForeignObject.class, fetch = FetchType.EAGER)
#JoinColumn(name = "FOREIGN_OBJECT_ID", referencedColumnName = "FOREIGN_OBJECT_ID",
nullable = false, insertable = false, updatable = false)
private ForeignObject foreignObject;
#Column(name = "FOREIGN_OBJECT_ID")
private Integer foreignObjectId;
You are never setting the relationship but only the integer field.
This will not work as a call to EntityManager#persist (via myObjectRepository.save) simply takes the existing object and makes it persistent i.e. nothing is going to trigger setting the reference to ForeignObject.
#Transactional
public MyObject create(MyObject myObject) {
//createdMyObject and myObject are same instance
MyObject createdMyObject = this.myObjectRepository.save(myObject);
return createdMyObject;
}
This will not work as the same instance (i.e. the one you created without the relationship set) will simply be retrieved from the Hibernate's first level cache:
#Transactional
public MyObject create(MyObject myObject) {
//createdMyObject, myObject and (due to 1st level cache)
//object returned from query are same
MyObject createdMyObject = this.myObjectRepository.save(myObject);
return this.myObjectRepository.findById(createdMyObject.getMyObjectId());
}
You can probable get method 2 to work by doing the following however the correct solution is to remove the Integer field and have the relationship set correctly. A Spring MVC controller should automatically set the reference from the ID on POST/PUT request with {... "foreignObject" : 3 ...}
#PersistenceContect
EntityManager em;
#Transactional
public MyObject create(MyObject myObject) {
this.myObjectRepository.saveAndFlush(myObject);
em.clear(); //force reload from database
return this.myObjectRepository.findById(createdMyObject.getMyObjectId());
}
I am new to this forum and hibernate. I am having a problem with hibernate many-to-one mapping.
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "DTE_ID")
#NotNull
private Dte raisedByDte;
This is the code I am using in main object and foreign key is DTE_ID. But when I am trying to save it is updating all fields in referenced table. My reference object is as follows:
#Entity
#Table(name = "DTE_MASTERS", uniqueConstraints = #UniqueConstraint(columnNames = "DTE_NAME"))
public class Dte {
#Id
#Column(name="DTE_ID", updatable = false, nullable = false)
private int dte_id;
#Column(name="DTE_NAME")
private String dte_name;
public Dte() {
super();
// TODO Auto-generated constructor stub
}
public Dte(int dte_id, String dte_name) {
super();
this.dte_id = dte_id;
this.dte_name = dte_name;
}
public int getDte_id() {
return dte_id;
}
public void setDte_id(int dte_id) {
this.dte_id = dte_id;
}
public String getDte_name() {
return dte_name;
}
public void setDte_name(String dte_name) {
this.dte_name = dte_name;
}
I want to restrict the update of DTE_MASTERS when I am inserting ..can some body please guide me through this?
You have to remove the cascade option from the mapping. It enforces the same operation as was performed on the parent object.
I am guessing that you are doing merge() on the main object.. and with that option, the merge() (which would result in an update if the entity is not new) will also be invoked on the Dte dependency:
#ManyToOne(fetch = FetchType.EAGER)
Also, keep in mind that if you any non-transient field within the Dte entity once its loaded with the main entity, all of the changes will be commited implicilty upon the transaction end.
In order to prevent that you would need to perform session.evict(dte); so that any changes will not get persisted in the database even if they were performed in within the transactional method.
I have 3 tables that have a hierarchical relationship:
Page (Grandmother)
public class Page extends BaseDAO {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "page_id", unique = true, nullable = false)
public Integer getPageId() {
return this.pageId;
}
#OneToMany(fetch = FetchType.EAGER, mappedBy = "page", cascade=CascadeType.ALL, orphanRemoval=true)
#NotFound(action = NotFoundAction.IGNORE)
public Set<PageWell> getPageWells() {
return this.pageWells;
}
}
PageWell (Mother)
public class PageWell extends BaseDAO {
#Id
#Column(name = "page_well_id", unique = true, nullable = false)
public int getPageWellId() {
return this.pageWellId;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "page_id", nullable = false)
public Page getPage() {
return this.page;
}
#OneToMany(fetch = FetchType.EAGER, mappedBy = "pageWell", cascade=CascadeType.ALL)
public Set<PageComponentAttribute> getPageComponentAttributes() {
return this.pageComponentAttributes;
}
}
PageComponentAttribute (Daughter)
public class PageComponentAttribute extends BaseDAO {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "page_component_attribute_id", unique = true, nullable = false)
public Integer getPageComponentAttributeId() {
return this.pageComponentAttributeId;
}
#ManyToOne(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
#JoinColumn(name = "page_well_id", nullable = false)
public PageWell getPageWell() {
return this.pageWell;
}
}
The primary keys for all three tables are AutoIncrement in MySQL. The expected behavior is that when I save the Page, all PageWell objects get saved, and all PageComponentAttribute objects also get saved.
For some reason, it is working correctly for the Grandmonther -> Daughter relationship. But in the case of the Mother -> Daughter relationship, the Daughter's foreign key is set to 0 every time. This was obviously causing a constraint violation. I have temporarily removed the FK constraint on that relationship, and the record makes it into the table, but the FK is still 0.
My save code looks like this:
Page page = getPage(request); //getPage() finds an instance of page, or creates and persists a new instance if none exists.
Set<PageWell> wells = page.getPageWells();
wells.clear(); //delete all related PageWell objects so we can re-create them from scratch
page = pageHome.merge(page);
wells = page.getPageWells();
PageWell pageWell;
// Now create a new PageWell and set up bi-directonal mapping with Page. This part works great.
pageWell = new PageWell();
pageWell.setPage(page);
wells.add(pageWell);
// Now do the exact same thing with the PageComponentAttribute objects
PageComponentAttribute pca = new PageComponentAttribute();
pca.setPageWell(pageWell);
pca.getPageWell().getPageComponentAttributes().add(pca);
// Now save the Page
page = pageHome.merge(page);
When I check the database, the FK in the PageComponentAttribute table is set to 0. Again, I have temporarily removed the FK constraint from MySQL just to allow the record to save without an exception, but besides that, what am I doing wrong?
I would try to do one of the things, or all:
1) Remove the cascade from the #ManyToOne. In general thats not a good idea to have it configured like that. It essentially makes sense only for #OneToMany and #OneToOne.
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "page_well_id", nullable = false)
public PageWell getPageWell() {
return this.pageWell;
}
2) Try using the Hibernate cascade configuration instead of the JPA one:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "pageWell")
#Cascade(CascadeType.ALL)
public Set<PageComponentAttribute> getPageComponentAttributes() {
return this.pageComponentAttributes;
}
There may be some small differences, see: article
3) Not sure why you invoke merge twice on the page entity. I would just stick to one at the very end.
4) Last workaround that comes to my mind would be performing an explicit flush here:
pageWell = new PageWell();
pageWell.setPage(page);
wells.add(pageWell);
session.flush();
and then:
PageComponentAttribute pca = new PageComponentAttribute();
pca.setPageWell(pageWell);
pca.getPageWell().getPageComponentAttributes().add(pca);
session.merge(pageWell);
In theory, pageWell should have the primary already generated because of the flush and it should not be 0 anymore.
I wish i had a testing environment right now to test this properly.
In the unlikely chance that someone has made the same bone-headed mistake I've made, the problem was that the PageWell entity's primary key didn't have a Generation strategy. I added that and it fixed my problem.
#GeneratedValue(strategy = IDENTITY)
I have created some Hibernate mappings with Hibernate 4.3.8.
#Entity
#Table(name = ErrorEntity.TABLE_ID)
#XmlRootElement(name = ErrorEntity.XML_ROOT_TAG)
public class ErrorEntity {
/**
*
*/
private static final long serialVersionUID = 8083918635458543738L;
public static final String TABLE_ID = "Error";
public static final String ERRORCODE = "error_code";
public static final String ENV_ID = "envid";
private Integer error_code;
private Integer envId;
private EnvironmentEntity environment;
public ErrorEntity() {
}
#Id
#Column(name = ErrorEntity.ERRORCODE)
public Integer getError_code() {
return error_code;
}
public void setError_code(Integer errorcode) {
this.error_code = errorcode;
}
#Column(name = ErrorEntity.ENV_ID)
public Integer getEnvId() {
return envId;
}
public void setEnvId(Integer envId) {
this.envId = envId;
}
#ManyToOne
#JoinColumn(name = ErrorEntity.ENV_ID, referencedColumnName = EnvironmentEntity.ENV_ID, insertable = false, updatable = false)
public EnvironmentEntity getEnvironment() {
return environment;
}
public void setEnvironment(EnvironmentEntity environment) {
this.environment = environment;
}
}
As you can see the mapping property ENV_ID is mapped twice.
Thisway I thought I would be able to set the JoinColumn value without querying the database to get the mapped object because I have the JoinColumn value at this point.
The value of ENV_ID is written correctly to the database but if I query this ErrorEntity later and try to get the EnvironmentEntity the reference is null.
ErrorEntity error = (ErrorEntity) criteria.uniqueResult();
System.out.println(error.getEnvironment().getName());
getEnvironment() returns null.
Any ideas how to achieve this?
Edit
It was working like expected to create a new object with the PK set.
Now I have a special situation where it does not work.
I need to reference another object where the joincolumn is not the PK. I know that the value i will join on is unique but there are also some duplicate values i will not join on.
However Hibernate seems to be unable to map this relationship automatically.
ErrorEntity error = new ErrorEntity();
SignalEntity signal = new SignalEntity();
signal.setName(signalName);
error.setSignal(signal);
The problem is that I do not have the signalID (PK) in that situation. The other idea would be to query the db but thats too slow.
I tried to create an composite PK with 3 columns but this breaks the logic at another place.
Is it possible to create two independent PK's?
The ErrorEntity has two ErrorEntity.ENV_ID mappings, which unless you use #MapsId then it's a configuration issue.
You should have an env_id column in EnvironmentEntity table and just the:
#ManyToOne
#JoinColumn(name = ErrorEntity.ENV_ID, referencedColumnName = EnvironmentEntity.ENV_ID, insertable = false, updatable = false)
public EnvironmentEntity getEnvironment() {
return environment;
}
mapping in ErrorEntity.
My suggestion is to remove this:
#Column(name = ErrorEntity.ENV_ID)
public Integer getEnvId() {
return envId;
}
To set the envId directly without querying the database and request the whole EnvironmentEntity, you can do something like this:
errrorEntity.setEnvironment(new EnvironmentEntity());
errrorEntity.getEnvironment().setEnvId(envId);
This is not a JPA standard requirement but Hibernate supports it.
Inside a service class, I have a method that is called from a #Transactional method. I have verified that I have a transaction active at the point this code is called. I realize that I don't have a DA layer when I should, but I am working with a legacy application that makes doing things the 'right' way more of a hassle than it's worth at this point.
The mappings look like this:
public class Foo {
private String id;
private Bar bar;
#Id
#Column(name = "FOO_ID", unique = true, nullable = false, length = 16)
#GeneratedValue(generator = "blahIdSeq")
#GenericGenerator(name = "blahIdSeq",
strategy = "org.blah.CustomIdGenerator")
public String getId() {return id;}
#JoinColumn(name = "FOO_ID")
#OneToOne(fetch = FetchType.LAZY, optional = false)
public Bar getBar() { return bar; }
// SETTERS INCLUDED
}
public class Bar {
private String id;
private Foo foo;
#Id
#Column(name = "FOO_ID")
#GeneratedValue(generator = "someSeq")
#GenericGenerator(name = "someSeq",
strategy = "foreign",
parameters = {
#Parameter(name = "property", value = "foo")
})
public String getId() { return id; }
#OneToOne(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn(name = "FOO_ID")
public Foo getFoo() { return foo; }
// SETTERS INCLUDED
}
The method looks something like this:
public String createFoo(Foo foo) {
Session ses = getSessionFactory().getCurrentSession();
Bar bar = new Bar();
bar.setFoo(foo);
foo.setBar(bar);
ses.save(foo);
ses.save(bar);
System.out.println(foo.getId()); // yields the ID value set by generator
System.out.println(bar.getId()); // yields same ID value as above
ses.flush();
ses.refresh(foo);
}
Now, with org.hibernate.SQL logging set to DEBUG, I can see that the insert statements for both Foo and Bar are created, but the refresh after the flush is called throws a org.hibernate.UnresolvableObjectException: No row with the given identifier exists exception.
What could cause this? The database used is Oracle 11gR2.
UPDATE
I have narrowed my issue down to sessions. It seems that calling the currentSession.flush() is not writing the data to the database as expected for the refresh. If I comment out the rest of the method, it will commit at the end and everything will be in the database.
Doing the flush/refresh will not return the hydrated object, however, so I cannot use the database-populated values (set by column defaults) later on in my transaction. I also cannot split the transaction into multiple ones because I need to be able to rollback at any point in the method.
Any ideas as to why the flush is not giving me accessible data in the database?
ANOTHER UPDATE
I have moved a lot of code around just to try and isolate the issue, and I'm still having problems. I also got rid of the relationship between the two entities to try and handle everything manually, just to see if that would fix the problem. Considering all the comments from Steve, here's what I have now:
public class Foo {
private String id;
private Bar bar;
#Id
#Column(name = "FOO_ID", unique = true, nullable = false, length = 16)
#GeneratedValue(generator = "blahIdSeq")
#GenericGenerator(name = "blahIdSeq",
strategy = "org.blah.CustomIdGenerator")
public String getId() {return id;}
// SETTERS INCLUDED
}
public class Bar {
private String id;
private Foo foo;
#Id
#Column(name = "FOO_ID")
public String getId() { return id; }
// SETTERS INCLUDED
}
#Service('fooService')
#Transactional(readOnly = true)
class FooService {
#Autowired
SessionFactory sessionFactory // populated using Spring config:
// org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean
#Transactional(readOnly = false)
public void doSomeStuff(Foo fooToSave) {
sessionFactory.getCurrentSession().saveOrUpdate(fooToSave);
Bar bar = new Bar(fooToSave); // this populates the Bar.Id field
sessionFactory.getCurrentSession().saveOrUpdate(bar);
sessionFactory.getCurrentSession().flush();
sessionFactory.getCurrentSession().refresh(fooToSave); // exception thrown here
}
}
YET ANOTHER UPDATE
After quite a bit of playing around in Oracle-land to make sure that the SQL was running on the same session and the like, I've found the issue. Even though Hibernate is logging that the SQL bind variables are being set, they actually are not. Using Oracle 11gR2's V$SQL_BIND_CAPTURE, I was able to see using the SQL ID that was executed last (verified to be the insert statement) had 24 bind variables and not one of them ever had a value bound to it. Still not sure what's causing the values to be blank, but I am quite a bit closer to finding my answer. It has to be a problem with my mappings, which I cannot put here in entirety.
Being bind variables, I'm guessing that Oracle doesn't throw a fit about not being able to insert. JDBC typically just returns the number of rows inserted for an INSERT statement for verification, but I'm not sure exactly how the Hibernate abstraction handles this stuff. I am currently using Hibernate 3.6.10 -- upgraded from 3.6.5 to see if it might fix the issue. It didn't. :P
I'VE BEEN MISLEAD
Ignore that "YET ANOTHER UPDATE" section, above. The bind variables seem like they don't show up in the V$SQL_BIND_CAPTURE view until the transaction has been committed. Back to the drawing board.
ANOTHER REVISION - I SWEAR I'M GONNA GET BANNED
I decided to go back to basics. What have I changed since it was in a working state? Mostly mappings. A few service layer items were also changed, but it was mostly moving our Hibernate mappings from XML to annotations. So I took the same service method I've been playing with, commented out all the other stuff, and tried doing the very same thing as what I'm trying to do with Foo using another persistent object type. Guess what? That works. The only link that could be causing my heartache at this point is the mapping I have for Foo. I doubt my employer would like me to just throw full source up on SO, so I'll probably have to just figure this one out on my own. I will post the answer in some capacity when I finally figure it out.
SUCCESS! BUT I'M NOT SURE WHY...
Here's the code that was giving me trouble. Keep in mind that BAZ is a linking table that has a composite ID made up with an #Embeddable (just called "key" for this example), consisting of FOO_ID referencing a row in the FOO table and a STATE_ID referencing another table.
public class Foo {
// OTHER FIELDS INCLUDING IDs AND SUCH
private Baz bazOfDoom;
private Baz bazOfLight;
private Set<Baz> allTheBaz;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumns({
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID", insertable = false, updatable = false, nullable = false)
#JoinColumn(name = "DOOM_ID", referencedColumnName = "STATE_ID", insertable = false, updatable = false, nullable = false)
})
public Baz getBazOfDoom() { return bazOfDoom; }
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumns({
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID", insertable = false, updatable = false, nullable = false)
#JoinColumn(name = "LIGHT_ID", referencedColumnName = "STATE_ID", insertable = false, updatable = false, nullable = false)
})
public Baz getBazOfLight() { return bazOfLight; }
#OneToMany(mappedBy = "key.foo", fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
public Set<Baz> getAllTheBaz() { return allTheBaz; }
}
I removed the cascades and it worked. I don't know why. Whoever can explain that will get the "correct answer" mark from me. :)
It seems that your object doesn't own an identifer for your object after saving it to database, leading thus to your exception when calling refresh().
Indeed, assume your database tables own primary key defined as auto-increment.So, when you save your first Foo object, primary key column is valued as: 1.
However, Hibernate has to be aware of this newly generated identifier after calling save() method !
The best way to do this is to expect Hibernate to reaffect the good identifier as soon as the object is saved into database.
Thus, you might miss this line within your entity classes in order to provide identifier automatically when object is saved in database:
#GeneratedValue(strategy = GenerationType.AUTO)
Of course, you don't have to autogenerate them and rather can manually precise it.