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.
Related
I have two tables in my database:
1. Warehouse
2. WarehouseItem
Relation between them are like listed below:
#Entity
#Table(name = "warehouse")
public class WarehouseModel {
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy="warehouse")
private List<WarehouseItemModel> _items;
and
#Entity
#Table(name = "warehouseItem")
public class WarehouseItemModel {
#ManyToOne
public WarehouseModel warehouse;
and now I want to SELECT all the objects of the entity WarehouseModel:
public List getObjects(Class pClass)
{
startTime();
connect();
Session session = getSession();
Transaction lTransaction = session.beginTransaction();
List lRet = session.createCriteria(pClass).list();
lTransaction.commit();
endTime("getObjects: " + lRet.size() + " objects");
Collections.reverse(lRet);
return lRet;
}
In my database I have:
1x object in the table: Warehouse (WarehouseModel.java)
5x objects in the table: WarehouseItem (WarehouseItemModel.java)
When I want to retrive all the Warehouses including related WarehouseItems:
databaseConnector.eDocumentConnector.getObjects(WarehouseModel.class)
the result is:
- 5x the same object of WarehouseModel
It seems that there is dependancy that I always get as much entities of the same WarehouseModel as there is WarehouseItemModels inside field WarehouseModel._items
How to fix it and why it happens? (I have more relations like this one in my project and if it happends here, maybe it happends also in the other places)
Project details:
- Java 1.8
- Hibernate 5.0.7
- database: PostgreSQL 9.5.2
It is solved by using DISTINCT in hibernate query:
session.createCriteria(pClass).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();
More importantly, you should understand how a 1-to-many relationship works in SQL.
The raw select statement is going to be something like
SELECT
.
.
<what columns you want>
.
FROM
warehouse w
JOIN warehouseItem wi ON (w.id = wi.warehouse_id)
WHERE
<whatever you need to filter on>
When you join, you're always going to get the number of rows that are related to the primary table's ID. Since there are 5 rows in the child table, 5 rows are returned, where the warehouse columns are duplicated each time but the warehouseItem columns are unique to each row.
So, here is the problem that I got.
I am building a database model structure by JPA which has a many to many relation, here is the class model
public class UserActivityRelation{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id",nullable = false)
private User participant;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "activity_id",nullable = false)
private Activity activity;
}
here is the corresponding DAO code that I followed the spring-doc spring-boot-jpa
public List<UserActivityRelation> findDistinctParticipantByParticipantAndActivity(User user, Activity activity);
hoping to distinct rows with same user_id, but it came with an unexpected result, the sql sentence is as follows:
select distinct useractivi0_.id as id1_1_, useractivi0_.created_time
as created_2_1_, useractivi0_.modified_time as modified3_1_,
useractivi0_.activity_id as activity5_1_, useractivi0_.user_id as
user_id6_1_, useractivi0_.status as status4_1_ from act_users
useractivi0_ left outer join users user1_ on useractivi0_.user_id=user1_.id
left outer join activities activity2_ on
useractivi0_.activity_id=activity2_.id where (user1_.id is null)
and (activity2_.id is null)
the sql code seems really complex, but the point is clear, findDistinct only has effect on the primary key id rather than the participant----user_id, and it uses a lot of left outer join...
At first I thought I write the method wrong, so I test it with another method name
findDistinctHEHEByParticipantAndActivity , as you can see it has HEHE in the middle which has nothing do to with model class, neither a property nor a method... and it just gave an identical sql sentence, which I was so confused to see...
So, is it just the way that JPA with hibernate works or did I do it wrong?
I'm trying to follow the JPA tutorial and using ElementCollection to record employee phone numbers:
PHONE (table)
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
Short version
What I need is a class like this:
#Entity
#Table(name="Phones")
public class PhoneId {
#Id
#Column(name="owner_id")
long owner_id;
#Embedded
List<Phone> phones;
}
that stores each person's phone numbers in a collection.
Long version
I follow the tutorial code:
#Entity
#Table(name="Phones")
public class PhoneId {
#Id
#Column(name="owner_id")
long owner_id;
#ElementCollection
#CollectionTable(
name="Phones",
joinColumns=#JoinColumn(name="owner_id")
)
List<Phone> phones = new ArrayList<Phone>();
}
#Embeddable
class Phone {
#Column(name="type")
String type = "";
#Column(name="number")
String number = "";
public Phone () {}
public Phone (String type, String number)
{ this.type = type; this.number = number; }
}
with a slight difference that I only keep one table. I tried to use the following code to add records to this table:
public static void main (String[] args) {
EntityManagerFactory entityFactory =
Persistence.createEntityManagerFactory("Tutorial");
EntityManager entityManager = entityFactory.createEntityManager();
// Create new entity
entityManager.getTransaction().begin();
Phone ph = new Phone("home", "001-010-0100");
PhoneId phid = new PhoneId();
phid.phones.add(ph);
entityManager.persist(phid);
entityManager.getTransaction().commit();
entityManager.close();
}
but it keeps throwing exceptions
Internal Exception: org.postgresql.util.PSQLException: ERROR: null
value in column "type" violates not-null constraint Detail: Failing
row contains (0, null, null). Error Code: 0 Call: INSERT INTO Phones
(owner_id) VALUES (?) bind => [1 parameter bound] Query:
InsertObjectQuery(tutorial.Phone1#162e295)
What did I do wrong?
Sadly, i think the slight difference that you only keep one table is the problem here.
Look at the declaration of the PhoneId class (which i would suggest is better called PhoneOwner or something like that):
#Entity
#Table(name="Phones")
public class PhoneId {
When you declare that a class is an entity mapped to a certain table, you are making a set of assertions, of which two are particularly important here. Firstly, that there is one row in the table for each instance of the entity, and vice versa. Secondly, that there is one column in the table for each scalar field of the entity, and vice versa. Both of these are at the heart of the idea of object-relational mapping.
However, in your schema, neither of these assertions hold. In the data you gave:
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
There are two rows corresponding to the entity with owner_id 1, violating the first assertion. There are columns TYPE and NUMBER which are not mapped to fields in the entity, violating the second assertion.
(To be clear, there is nothing wrong with your declaration of the Phone class or the phones field - just the PhoneId entity)
As a result, when your JPA provider tries to insert an instance of PhoneId into the database, it runs into trouble. Because there are no mappings for the TYPE and NUMBER columns in PhoneId, when it generates the SQL for the insert, it does not include values for them. This is why you get the error you see - the provider writes INSERT INTO Phones (owner_id) VALUES (?), which PostgreSQL treats as INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null), which is rejected.
Even if you did manage to insert a row into this table, you would then run into trouble on retrieving an object from it. Say you asked for the instance of PhoneId with owner_id 1. The provider would write SQL amounting to select * from Phones where owner_id = 1, and it would expect that to find exactly one row, which it can map to an object. But it will find two rows!
The solution, i'm afraid, is to use two tables, one for PhoneId, and one for Phone. The table for PhoneId will be trivially simple, but it is necessary for the correct operation of the JPA machinery.
Assuming you rename PhoneId to PhoneOwner, the tables need to look like:
create table PhoneOwner (
owner_id integer primary key
)
create table Phone (
owner_id integer not null references PhoneOwner,
type varchar(255) not null,
number varchar(255) not null,
primary key (owner_id, number)
)
(I've made (owner_id, number) the primary key for Phone, on the assumption that one owner might have more than one number of a given type, but will never have one number recorded under two types. You might prefer (owner_id, type) if that better reflects your domain.)
The entities are then:
#Entity
#Table(name="PhoneOwner")
public class PhoneOwner {
#Id
#Column(name="owner_id")
long id;
#ElementCollection
#CollectionTable(name = "Phone", joinColumns = #JoinColumn(name = "owner_id"))
List<Phone> phones = new ArrayList<Phone>();
}
#Embeddable
class Phone {
#Column(name="type", nullable = false)
String type;
#Column(name="number", nullable = false)
String number;
}
Now, if you really don't want to introduce a table for the PhoneOwner, then you might be able to get out of it using a view. Like this:
create view PhoneOwner as select distinct owner_id from Phone;
As far as the JPA provider can tell, this is a table, and it will support the queries it needs to do to read data.
However, it won't support inserts. If you ever needed to add a phone for an owner who is not currently in the database, you would need to go round the back and insert a row directly into Phone. Not very nice.
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.
This is similar, but not identical, to:
Hibernate criteria query on different properties of different objects
I have a SpecChange record, which has a set of ResponsibleIndividuals; these are User records mapped by a hibernate join-table association. I want to create a Criteria query for a SpecChange which has the specified User in its ResponsibleIndividuals set, OR some other condition on the SpecChange.
I'm omitting most of the code for clarity, just showing the relevant annotations:
#Entity
class SpecChange {
#OneToMany
#JoinTable(name = "ri_id_spec_change_id", joinColumns = { #JoinColumn(name = "spec_change_id") }, inverseJoinColumns = #JoinColumn(name = "ri_id"))
#AccessType("field")
public SortedSet<User> getResponsibleIndividuals() { ... }
#Id
#Column(name = "unique_id")
#AccessType("field")
public String getId() { ... }
}
#Entity
class User { ... }
//user does not have a SpecChange association (the association is one-way)
What I want to do:
User currentUser = ...;
Criteria criteria = session.createCriteria(SpecChange.class);
...
criteria.add(Restrictions.disjunction()
.add(Restrictions.eq("responsibleIndividuals", currentUser))
.add(...)
);
criteria.list();
This generates wrong SQL:
select ... from MY_DB.dbo.spec_change this_ ... where ... (this_.unique_id=?)
...and fails:
java.sql.SQLException: Parameter #2 has not been set.
(I omitted another condition in the where clause, hence parameter #2 is the one shown. I am sure that 'currentUser' is not null.)
Note that the restriction references the wrong table: this_, which is SpecChange, not the User table
I tried a dozen different tricks to make it work correctly (including creating an alias, as mentioned in the previous post above). If there is a way to do it using the alias, I wasn't able to determine it.
The following DOES work (but doesn't accomplish what I need, since I can't use it in a disjunction):
criteria.createCriteria("responsibleIndividuals").add(Restrictions.idEq(currentUser.getId()));
[Edit: a workaround for what seems like a bug in Hibernate, using HQL]
select sc
from com.mycompany.SpecChange sc
left join fetch sc.responsibleIndividuals as scResponsibleIndividuals
where scResponsibleIndividuals = :p1 or sc.updUser = :p1
order by sc.updDate desc
This won't work without the alias "scResponsibleIndividuals"