I've got a legacy DB2 database which I can't change. It can't save null-values so all foreign keys which are empty are set to zero (0).
While reading this is no problem, I just set the not found action to ignore and it returns me null instead of the child-object.
But I have a problem the other way around. When I want to save a null-object it should set the foreign key in my table to 0, but it doesn't know how to do that and throws me an "null value not allowed"-exception.
#OneToOne()
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumn(name = "robj02", referencedColumnName = "objnr")
private Objekt robj02;
Is there a way to define a default value or something similar? Does anyone have an idea to help me?
You can correct your mapping in the following way:
#Entity
public class YourEntity
{
#Column(name = "robj02")
private Long objektId;
#OneToOne
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumn(name = "robj02", referencedColumnName = "objnr", insertable = false, updatable = false)
private Objekt robj02;
// getters/setters
}
and then just persist it in the following way:
YourEntity entity = new YourEntity();
entity.setObjektId(0L);
// ...
entityManager.persist(entity);
Dirty hack but something like
void setRobj02(Objeckt robj02) {
this.robj02 = (robj02 != null)
? robj02
: Objekt.ZERO // you should create it first
}
Related
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'm having a hard time understanding this JPA behavior which to me doesn't seem to follow the specification.
I have 2 basic entities:
public class User {
#Id
#Column(name = "id", unique = true, nullable = false, length = 36)
#Access(AccessType.PROPERTY)
private ID id;
#OrderBy("sequence ASC")
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.REMOVE })
private final Set<UserProfile> userprofiles = new HashSet<UserProfile>(0);
//Ommiting rest of fields since they aren't relevant
}
public class UserProfile {
#Id
#Column(name = "id", unique = true, nullable = false, length = 36)
#Access(AccessType.PROPERTY)
private ID id;
#NotNull
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "userID", nullable = false, foreignKey = #ForeignKey(name = "FK_UserProfile_User"))
private User user;
//Ommiting rest of fields since they aren't relevant
}
As you can see I only have cascading set to REMOVE, the behavior will be the same if I don't have cascade set at all.
Now if I call:
User user = new User();
user.setId(UUIDGenerator.generateId());
UserProfile userProfile = new UserProfile();
userProfile.setId(UUIDGenerator.generateId());
userProfile.setUser(user);
user.getUserProfiles().add(userProfile);
em.merge(user);
merge will throw an exception.
I see Hibernate is executing a SQL query against the UserProfile table:
select userprofil0_.userProfileID as userProf1_4_0_, userprofil0_.profileID as profileI3_4_0_, userprofil0_.sequence as sequence2_4_0_, userprofil0_.userID as userID4_4_0_ from UserProfile userprofil0_ where userprofil0_.userProfileID=?
And then it will throw an exception
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find com.mytest.domain.UserProfile with id 6aaab891-872d-41e6-8362-314601324847;
Why is this query even called?
Since I don't have cascade type set to MERGE in userprofiles my expectation would be that JPA/Hibernate would simply ignore the entities inside userprofiles set and only insert/update the user record, doesn't this go against the JPA specs?
If I change cascadetype to MERGE things will work as expected and both User and UserProfile will be added to the database, so no problem there. What puzzles me is why is Hibernate querying the database and erroring out about an entity that's not supposed to be merged at all since I don't have it set to cascade.
This is more of an academic scenario that I ran into, of course I could simply clear the userprofiles set and things would work, but I'm trying to understand why the above behavior happens since I'm probably missing some crucial piece of information about how merge works. It seems it will always try to attach all entities to the session regardless cascade type being set or not.
Why is this query even called?
It's because you are trying to merge the entity, in JPA merge() is used to make the entity managed/attached. To "merge" User, JPA needs to still maintian the references it holds(UserProfile). In your case its not trying to persist UserProfile its trying to get a reference to it to merge User. Read here
If you use persist rather than merge this should not happen.
jpa class is this:
public class JpaAlertConfiguration{
...
#OneToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JoinColumn(name = "alert_type_id", nullable = false, referencedColumnName = "id", insertable = false, updatable = false)
private JpaAlertType alertType;
}
Create Entity is called from this class method.
#Override
#Transactional
public JpaAlertConfiguration createEntity(JpaAlertConfiguration entity) throws Exception
{
getAuthorizer().checkCreate(ENTITY_NAME);
validateFields(entity);
getAuthorizer().checkAccountAccess(entity.getAccount().getId(), null);
entity = attachAssociatedEntities(entity);
// entity.setStatusColumns(new StatusColumns().setStatus(ResourceStatus.Disabled));
entity = super.createEntity(entity);
entity = transformResult(entity);
return entity;
}
I made sure that till this line is called, entity = super.createEntity(entity);, JpaAlertType id value is populated, which is nothing but "alert_type_id" of alertConfig table.
I am getting exception:
Caused by: org.postgresql.util.PSQLException: ERROR: null value in column "alert_type_id" violates not-null constraint
Detail: Failing row contains (3, Speed Alert, Speeding Alert, 5, t, null, Enabled, 0, 2016-09-06 17:56:30.556+05:30, 2016-09-06 17:56:30.58+05:30, 2016-09-06 17:56:30.556+05:30, null, f, f).
Please suggest
I think you are missing JPA persisting order, see the link below about JPA one-to-one insert, i hope this could help.
The issue is resolve:
Actually here in my scenario I was calling generic createEntity method. Not sure but I just removed all the complicate annotation from
alertType variable in JPAAlertConfiguration class
#OneToOne(optional = false)
#JoinColumn(name = "alert_type_id")
private JpaAlertType alertType;
and it worked. Thanks anyways
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()
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.