Why is my persistent object returning transient objects when fetching via a relationship?
ObjectContext context = BaseContext.getThreadObjectContext();
// Delete some employee schedules
List<EmployeeSchedule> employeeSchedules = this.getEmployeeSchedules();
for (EmployeeSchedule employeeSchedule : employeeSchedules) {
context.deleteObject(employeeSchedule);
}
// Add new schedules
for(int i = 0; i < someCondition; i++) {
EmployeeSchedule employeeSchedule = context.newObject(EmployeeSchedule.class);
addToEmployeeSchedules(employeeSchedule);
}
context.commitChanges();
List<EmployeeSchedule> es = getEmployeeSchedules(); // returns transient objects
It is inserting the data correctly into the database. Would this be an issue with stale data in the cache?
I'm answering my own question in case someone else get tripped up by this in the future.
I have a many-to-many relationship.
Employee - EmployeeSchedule - Schedule
According to the delete rules here: http://cayenne.apache.org/docs/3.0/delete-rules.html, I set the fields employee_id and schedule_id in the EmployeeSchedule to Nullify rule on delete.
I also had to configure the join table EmployeeSchedule by making employee_id and schedule_id primary keys in the Modeler and checking the "to Dep PK" checkbox in the employee and schedule dbEntity.
Relevant links: http://objectstyle.org/cayenne/lists/cayenne-user/2004/02/0017.html
http://grokbase.com/t/cayenne/user/085d70sysk/to-dep-checkbox-was-one-to-many-problem
Related
Is it possible to use GenerationType.IDENTITY with Transaction in Hibernate/Spring data?
I have an existing database, with an identity column in all tables. So, I have to use GenerationType.IDENTITY for it. But, when I create a new entity instance and change its state to managed with someRepository.save(...) method, the persistence provider can't acquire a new ID for that entity, because it must happen on flush time, at the end of the transaction.
If I create one entity, all works as expected. After save(), the entity goes to the managed state, the id is changed from NULL to 0 (zero), and at the flush time, the new ID for the row is generated by the database.
But what if we create two instances of the same entity class inside one transaction? The exception will be thrown, and this is justly because we have two different objects with same ID = 0. So, is there a way to deal with it without change strategy from IDENTITY to something else?
#Entity
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
Long id;
...
}
#Transactional
public void brokenCode() {
Customer one = new Customer();
Customer two = new Customer();
someRepository.save(one);
someRepository.save(two); <--- org.springframework.dao.DataIntegrityViolationException: A different object with the same identifier value was already associated with the session
}
CREATE TABLE [dbo].[Customer]
(
[id] [int] IDENTITY (1,1) NOT NULL,
...
CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED ([id] ASC)
WITH (PAD_INDEX = OFF
, STATISTICS_NORECOMPUTE = OFF
, IGNORE_DUP_KEY = OFF
, ALLOW_ROW_LOCKS = ON
, ALLOW_PAGE_LOCKS = ON
, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [DATA]
)
UPD!
The reason is the table has "trigger instead of insert". In that trigger some fields are calculated based on other fields. And the identity doesn't return to Hibernate.
ALTER trigger [dbo].[Customer] on [dbo].[Customer]
instead of insert
as
if ##rowcount = 0 return
set nocount on
Set dateformat dmy
Insert into dbo.Customer
(f1, f2, ....)
Select
I.f1,
isnull(f2,newid()),
...
from Inserted I
So. Is there a way to somehow return identity to Hibernate from that trigger?
Thanks in advance!
I have a many to many relationship between a User and a Trip entity with two foreign keys. I am trying to add a Trip to the User and even though there is a such an ID in my Users table, I receive the following exception:
Caused by: org.postgresql.util.PSQLException: ERROR: insert or update on table "UserTrip" violates foreign key constraint "user_id"
Detail: Key (user_id)=(1) is not present in table "User".
User side of the many to many relationship:
#ManyToMany
#JoinTable(name = "\"UserTrip\"", schema = "\"TransportSystem\"", joinColumns = {
#JoinColumn(name = "user_id") }, inverseJoinColumns = { #JoinColumn(name = "trip_id") })
private List<Trip> trips = new ArrayList<>();
Trip side of the many to many relationship:
#ManyToMany(mappedBy = "trips")
private List<User> users = new ArrayList<>();
DAO function to add a trip:
public void addTrip(int id, Trip trip) {
executeInsideTransaction(entityManager -> {
User user = entityManager.find(User.class, id);
user.getTrips().add(trip);
});
}
My little helper function to handle transactions within the same dao:
private void executeInsideTransaction(Consumer<EntityManager> action) {
EntityTransaction tx = entityManager.getTransaction();
try {
tx.begin();
action.accept(entityManager);
tx.commit();
} catch (RuntimeException e) {
tx.rollback();
throw e;
}
}
This is where I call to add the trip (don't think any more context is needed, if you wish, I can provide more.)
UserService userService = new UserService();
User user = userService.getById(1);
userService.addTrip(1, newTrip);
Things to note:
The entity is "User" but the table it is mapped to is called "Users" since in PostgreSQL the User is a reserved keyword.
I tried MERGE and REMOVE cascades and a lazy fetch type on the User side
I tried to pass the whole User object to the addTrip function and then use entityManager.merge() but then as read here on stackoverflow I decided to use entityManager.find() to load the user by id from the database directly and then add a role and commit the transaction. Unfortunately, both cases yield the same result (this exception).
Needless to say, there is a user_id = 1 in the database.
I would appreciate your input. I know there are many threads regarding this particular exception but honestly I seem unable to resolve it.
This has been resolved, to anyone wondering:
The problem comes from the fact that User is a reserved keyword in PostgreSQL. I tried multiple times to avoid problems with it, I created a Users table and mapped my User entity to Users table. So far there were no problems with it.
But once a joined table is involved, things get complicated. In my case, I had a UserTrip joined table with a many to many relationship and Hibernate is looking for a user_id from User. I found no way to explicitly tell hibernate to take Users rather than User, that's why I decided to name my table UsersTrip and everything has been resolved.
Lesson learned - avoid using words similiar to keywords where possible.
I have related objects consisting of parent entities such as Organisation.java which has object-typed child attributes as #OneToMany lists like activities (i.e. List activitiyList) (Activiy.java has its own object-typed attributes.
It is very easy to use JPA persistence to do CRUD operations of these objects on a database, but my current requirement forbids me to use JPA, and implement the same functionality using only-JDBC - which I'm not sure how to implement.
How could the same functionality be implemented via JDBC when both parent and child objects are created for the first time (i.e. with all of the objects having null IDs)?
Assuming you have a foreign key relationship between Organisation and Activity, you must create the parent first, then the child rows with the parent id.
You can do this with spring, here's an old post, but the principals remain the same.
To implement manually, your database must provide a mechanism by which to generate primary keys for a given table without having to create a row first. Oracle supports sequence.nextVal, so your database should support something similar.
I'm pseudo-coding this, you can fill in the blanks:
try{
connection.setAutoCommit(false)
//get organisation id first
String nextOrgIdSql = "select orgSeq.nextval from someVirtualTable" //depends on database
ResultSet orgIdRs = statement.executeQuery( nextOrgIdSql)
int orgId = -1
if( orgIdRs.next())
orgId = orgIdRs.getInt(1)
//create organisation first
String orgSql =
"Insert into ORGANISATION (ORGID, ...) values ("+ orgId + ",...)"
//create activities
for( Activity activity : organisation.getActivityList()){
String nextActvIdSql = "select activitySeq.nextval from someVirtualTable"
ResultSet actvIdRs = statement.executeQuery( nextActvIdSql)
int actvId = -1
if( actIdRs.next())
actvId = actvIdRs.getInt(1)
statement.execute(
"Insert INTO ACTIVITY (ACTVID, ORGID) values ("+actvId+","+orgId+")"
}
connection.commit()
}catch(SQLException e){
connection.rollback()
}
Hi Guys I have a problem with Hibernate when trying to write an array of object to the db. Essential I have an object built from a web service query. This object 'response' can have contain a maximum of ten 'unpaid items', my problem arises when I try to persist these.
Entity:
#Entity
#Table(name="TABLE_NAME")
public class AccountDetailsRROutput implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private String payeename;
private String typeunpd;
private BigDecimal unpdamt;
#Column(name="TRANSACTION_ID")
private long transactionId;
public AccountDetailsRROutput() {
super();
}
// plus all get/sets
}
//================================================================
// Populate the output for the repeating rows table
// which can contain a maximum of 10 unpaid items
//===============================================================
AccountDetailsRROutput outputRRTable[] = new AccountDetailsRROutput[response.getLineItems().length];
LOGGER.debug(METHOD_NAME, "Loop through the line items");
for (int i = 0; i < response.getLineItems().length; i++) {
//================================================================
// Ensure that we have an item so we don't write an empty row
//================================================================
if (response.getLineItems()[i].getTypeunpd() == null || response.getLineItems()[i].getTypeunpd() == "") {
LOGGER.debug(METHOD_NAME, "No unpaid item entry so break out of the the loop");
break;
}
else {
LOGGER.debug(METHOD_NAME, "We've got an unpaid item so add the details to the DB");
outputRRTable[i] = new AccountDetailsRROutput();
outputRRTable[i].setTransactionId(iTransactionID);
outputRRTable[i].setTypeunpd(response.getLineItems()[i].getTypeunpd());
outputRRTable[i].setPayeename(response.getLineItems()[i].getPayeeName());
outputRRTable[i].setUnpdAmt(response.getLineItems()[i].getUnpdAmt());
//================================================================
// Persist the output list DB object
//================================================================
LOGGER.debug(METHOD_NAME, "Persist repeating rows table DB object for line item: " + (i+1));
em_i.persist(outputRRTable[i]);
}
}
LOGGER.debug(METHOD_NAME, "Finished persisting repeating rows table DB object");
em_i.flush();
When I try this I get the following error:
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session:
I can get around this my changing emi.persist to emi.merge but it is only writing one element to the db. There can be duplicate records in this table and there is no pk.
You probably have more than one item with the same payeename. Try defining another id (for example an id composed of payeename and transactionID).
As per your hibernate mapping payeename is your primary key which means there can be only one entry per payeename in your database, the exception says NonUniqueObjectException which indicate that you are trying to persist another row with the same primary key.
Solution:
Before inserting, make sure there is no entry in the database with the primary key.
If the primary key already exist instead of insert a new record, do an update.
Hope it helps.
I have two entities: Questionnaire and QuestionnaireTime. Questionnaire's id is a foreign key in QuestionnaireTime. So the relationship in my QuestionnaireTime entity looks like this:
#JoinColumn(name = "questionnaireid", referencedColumnName = "id")
#ManyToOne(cascade = CascadeType.PERSIST)
private Questionnaire questionnaireid;
So what I'm trying to do is to add multiple QuestionnaireTime records for one Questionnaire. If I remove the CascadeType.PERSIST part in my relationship, my persist is not done. And when I use cascade, I get several new records in my main table Questionnaire and that's not what I want.
For example when I want to add three QuestionnaireTime's for a certain Questionnaire, the three records are inserted in my QuestionnaireTime table but also 3+1 records are added in Questionnaire.
If you need more explanation. This is my managed bean, the part that I'm trying to add multiple QuestionnaireTime records in one Questionnaire:
NB - current is my Questionnaire object
else if (current.getType().equals("frequent")) {
int iteration = 1;
currentQuestionnaireTime = new QuestionnaireTime();
if (!selectDateList.isEmpty()) {
for (String insertedDate : selectDateList) {
currentQuestionnaireTime.setId(0);
currentQuestionnaireTime.setQuestionnaireid(current);
getEjbQuestionnaireTimeFacade().create(currentQuestionnaireTime);
iteration++;
}
}
}
try {
getFacade().create(current); // my Questionnaire facade
} catch (EJBException ejbe) {
ejbe.getCause();
}
A few things,
questionnaireid - this is a very bad field name, questionnaire would make sense.
currentQuestionnaireTime.setId(0); - you should not be changing the id of an existing object, instead create a new object
getEjbQuestionnaireTimeFacade().create() - what does this do? If you need the reference to the current, then the current should be persisted first. If you EJB remote? If it is, then either make it local, or ensure you use merge() not persist(), as you new object has a reference to a detached object. Or find the reference in the current persistence context.