This question is related to previous question that I asked yesterday.
I have many-to-many relationship between Employee and SkillSet table, with additional column numberOfYears for each relation
employeeId skillSetId numberOfYears
10 101 2
Since I do not have ID column in EmployeeSkillSet table, I am using #IdClass to define the composite key
#Entity
class Employee {
private #Id Long id;
#OneToMany(mappedBy="employeeId")
private List<EmployeeSkillSet> skillSets;
}
class SkillSet {
private #Id Long id;
}
#IdClass(EmpSkillKey.class)
#Entity
class EmployeeSkillSet {
#Id
#Column("employee_id")
private Long employeeId;
#Id
#Column("skill_id")
private #Id Long skillId;
#ManyToOne
private Employee employee;
private int numberOfYears;
}
class EmpSkillKey{
private int employeeId;
private int skillId;
}
interface EmployeeRepository extends JPARepository{
List<Employee> getEmployeesBySkillSetSkillId(long id);
}
The above JPA repository method works fine and gives me list of Employees as per the skillSet ID. But when I try to iterate over the list and get the EmployeeSkillSet object then it throws error, as it tries to map to incorrect column employee instead of employeeId.
List<Employee> emps = employeeRepository.getEmployeesBySkillSetSkillId(101);
for(Employee e: emps){ // this line throws error
EmployeeSkillSet ess = e.getEmployeeSkillSet();
int n = ess.getNumberOfYears();
}
Query generated is something like this. (I have converted it to Employee use case, cannot share actual query)
select ud.employee_id , ud.employee_id , ud.employee , ud.employee_value , rd.employee_id
from employee_skill_set ud left outer join employee rd
on ud.employee=rd.employee_id
where ud.employee_id=?
Exception
WARN - SqlExceptionHelper - SQL Error: 207, SQLState: ZZZZZ
ERROR - SqlExceptionHelper - Invalid column name 'employee'.
org.hibernate.exception.GenericJDBCException: could not extract ResultSet
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:54)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:126)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:112)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:91)
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.getResultSet(AbstractLoadPlanBasedLoader.java:449)
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeQueryStatement(AbstractLoadPlanBasedLoader.java:202)
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:137)
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:102)
at org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer.initialize(AbstractLoadPlanBasedCollectionInitializer.java:100)
at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:693)
at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:92)
at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:1933)
at org.hibernate.collection.internal.AbstractPersistentCollection$4.doWork(AbstractPersistentCollection.java:559)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:261)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:555)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:143)
at org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:294)
May be I cannot define #Id employeeId and #ManyToOne employee in same class. But then how to resolve this?
Nevermind, I found out the solution. Annotated the #ManyToOne relation with #JoinColumn and with actual column name. Not sure why it asked for making updatable and insertable as false. Have to get my basics clear :)
#IdClass(EmpSkillKey.class)
#Entity
class EmployeeSkillSet {
#Id
#Column("employee_id")
private Long employeeId;
#Id
#Column("skill_id")
private #Id Long skillId;
#JoinColumn(name="employee_id", insertable=false, updatable=false)
#ManyToOne
private Employee employee;
private int numberOfYears;
}
Related
I have data being persisted in Spring of employees and "personal development plans". Employee is the dominant class, so to speak. It looks like this:
#Entity(name = "employee")
#Table(name = "EMPLOYEE")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "EMPLOYEE_ID")
private int id;
// etc...
}
Personal Development Plan looks like this:
#Entity(name = "pdp")
#Table(name = "PDP")
public class PersonalDevelopmentPlan implements Serializable {
#Id
#GeneratedValue
#Column(name = "PDP_ID")
private int id;
#ManyToOne(optional = false)
#JoinColumn(name = "EMPLOYEE_ID")
private Employee employee;
// etc..
}
In the database it is stored as a foreign key reference from PDP -> Employee.
I want to be able to load a PDP as it is in the database, with only employee id, but i always get the whole Employee object with all attributes. How do i do this?
I tried #ManyToOne(fetch = FetchType.Lazy) but this gives me the following error when fetching:
Type definition error: [simple type, class
org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]; nested
exception is
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No
serializer found for class
org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no
properties discovered to create BeanSerializer (to avoid exception,
disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference
chain:
java.util.ArrayList[0]->nl.kars.lms.model.pdp.PersonalDevelopmentPlan["employee"]->nl.kars.lms.model.Employee$HibernateProxy$AAwzPX4I["hibernateLazyInitializer"])
What am i doing wrong?
If you want only the ID, why not map it without the relationship ?
#Entity(name = "pdp")
#Table(name = "PDP")
public class PersonalDevelopmentPlan implements Serializable {
#Id
#GeneratedValue
#Column(name = "PDP_ID")
private int id;
#Column(name = "EMPLOYEE_ID")
private Long employeeId;
// etc..
}
It is actually how an ORM works... mapping table(relational side) to entities(object side). And mapping between entities is not done via ids but by entity references.
So either just persist the id (so remove strong relation) or use a projection query to just get back the employee Id.
You can create getter:
public Long getEmployeeId(){
return this.employee.getId();
}
Or you can change mapping to value private Long employeeId.
I had a column useless_id in table foo. This column is foreign key into other table.
I have mapped it like this
#Entity
#Table(name = "foo")
public class Foo{
#Column(name = "useless_id")
private Integer uselessId;
//...
}
Everything worked perfect. But I decided to change the name of column useless_id into useful_id.
After that appear problems. When I try to save an Foo object: session.save(new Foo(...)) I get Unknown column F.useless_id in 'where clause'.
The query is printed in console insert into foo (..., useful_id, ...) value (...)
In list of columns I don't see useless_id.
Why I get Unknow column useless_id in 'where clause' ? Why use where when insert?
It is was changed everywhere. Even in Foo object
I get this error only when try to save.
UPDATE(Foo class is Order Class and useful_id is customer_id):
#Entity
#Table(name = "orders")
public class Order{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column(name = "status")
private Integer status;
#Column(name = "customer_id")
private Integer customerId;
#Column(name = "shipping_address")
private String shippingAddress;
//setters getters
}
#Entity
#Table(name = "customers")
public class Customer{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
//setters getters
}
This is how I try to insert new object
//...
session.beginTransaction();
Order order = new Order();
//set random values. customer_id get valid value, it exists in customers
session.save(order);
session.getTransaction().commit();
session.close();
For DESCRIBE orders; command I get:
Field----------------Type-----------Null---Key---Default---Extra
id-------------------int(11)--------NO-----PRI---NULL------auto_increment
status---------------int(50)--------NO-----------NULL------
customer_id----------int(50)--------NO-----MUL---NULL------
shipping_address-----varchar(191)---NO-----------NULL------
I found the problem.
It raised from MySQL. I found it by tried to insert with SQL command, direct to MySQL. Same error.
So I was looking very carefully in db and I found the problem is from triggers. In one of triggers still use old name of column.
Now make sense: Unknow column useless_id in 'where clause'. That where clause was in trigger which try to find useless_id, but it no longer exists.
CONCLUSION: After change name of column, check triggers.
In your java class you changes column name from useless_id to userful_id, but same think you didnt changes in your DB structure due to which you see this error.
Query to fetch data:
JPA not able to read attributeId table.
-- select query to fetch data
select r,a from data r ,
Attributes a
where a.attributeId.type != 'test'
and r.typeid = a.attributeId.typeid
and r.deviceid=:deviceid order by r.typeid;
-- table1
#Entity
#Table(name = "data")
public class data {
#Id
#Column(name = "typeid")
private Integer typeid;
--- table 2
#Entity
#Table(name = "attributes")
public class Attributes implements Serializable {
#EmbeddedId
private Attributeid attributeId;
#Column
private String value;
-- Class with composite keys
#Embeddable
public class Attributeid implements Serializable {
#Column
private Integer typeid;
#Column
private String type;
#Column
private String attributename;
Your query is wrong. It's JPQL not SQL so you have to join with on clause.
It should be
select r, a from data r join Attributes a on r.typeid = a.attributeId.typeid
where a.attributeId.type != 'test'
and r.deviceid=:deviceid order by r.typeid;
Is the relationship to Attributes oneToOne or onToMany?
The question is why you don't map the relationship but the only the attributes?
I would like to find an entity using a critera with restriction on the value of an attribute of a second entity wich is a member of the embedded id of my first entity.
First entity :
#Entity
public class Car {
#EmbeddedId
private Id id = new Id();
private String color;
#Embeddable
public static class Id implements Serializable {
private static final long serialVersionUID = -8141132005371636607L;
#ManyToOne
private Owner owner;
private String model;
// getters and setters...
// equals and hashcode methods
}
// getters and setters...
}
Second entity :
#Entity
public class Owner {
#Id
#GeneratedValue (strategy = GenerationType.AUTO)
private Long id;
private String firstname;
private String lastname;
#OneToMany (mappedBy = "id.owner")
private List<Car> cars;
// getters and setters...
}
In this example, I would like to obtain the car with the color 'black', model 'batmobile' and the owner's firstname 'Bruce' (oops... spoiler ;) )
I tried to do something like that but it won't work :
List<Car> cars = session.createCriteria(Car.class)
.add(Restrictions.eq("color", "black"))
.add(Restrictions.eq("id.model", "batmobile"))
.createAlias("id.owner", "o")
.add(Restrictions.eq("o.firstname", "Bruce"))
.list();
Result :
Hibernate: select this_.model as model1_0_0_, this_.owner_id as owner_id3_0_0_, this_.color as color2_0_0_ from Car this_ where this_.color=? and this_.model=? and o1_.firstname=?
ERROR: Unknown column 'o1_.firstname' in 'where clause'
What is the right way to obtain what I want ?
update
I tried in hql :
String hql = "FROM Car as car where car.color = :color and car.id.model = :model and car.id.owner.firstname = :firstname";
Query query = em.createQuery(hql);
query.setParameter("color", "black");
query.setParameter("model", "batmobile");
query.setParameter("firstname", "Bruce");
List<Car> cars = query.getResultList();
It works but is there a way to do this with criteria ?
You forgot to add the #Column annotation on top of the firstname and lastname fields (and the color field in Car). In hibernate if a field is not annotated, it doesn't recognize it as a database field. This page should give you a good idea about how to set up your model objects.
NOTE: You can have the column annotation over the getters and be fine, but you didn't show the getters. Either place is fine.
Look at what HQL is spitting back out, specifically the statement (formated for easier reading):
select
this_.model as model1_0_0_,
this_.owner_id as owner_id3_0_0_,
this_.color as color2_0_0_
from Car this_
where
this_.color=?
and this_.model=?
and o1_.firstname=?
It looks like hibernate is translating the field "id.owner" to "o" as your alias told it to to, but for some reason it's not writing down that "id.owner=o" as intended. You may want to do some research into why it may be doing that.
As per https://hibernate.atlassian.net/browse/HHH-4591 there is a workaround.
You have to copy the needed relation-property of the #EmbeddedId (owner in this case) to the main entity (Car in this case) with insertable = false, updatable = false as follows
#Entity
public class Car {
#EmbeddedId
private Id id = new Id();
private String color;
#ManyToOne
#JoinColumn(name = "column_name", insertable = false, updatable = false)
private Owner owner;
#Embeddable
public static class Id implements Serializable {
private static final long serialVersionUID = -8141132005371636607L;
#ManyToOne
private Owner owner;
private String model;
// getters and setters...
// equals and hashcode methods
}
// getters and setters...
}
Then just create directly the alias instead of using the composite id property
List<Car> cars = session.createCriteria(Car.class)
.add(Restrictions.eq("color", "black"))
.add(Restrictions.eq("id.model", "batmobile"))
.createAlias("owner", "o")
.add(Restrictions.eq("o.firstname", "Bruce"))
.list();
This is the code:
#Entity
public class Dept {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#OneToMany(
mappedBy = "dept",
cascade = CascadeType.ALL
)
private List<Employee> employees = new ArrayList<Employee>();
public void addEmployee(Employee emp) {
this.employees.add(emp);
}
}
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column private Integer age;
public Employee(Integer a) {
this.age = a;
}
}
Then in a unit test I'm doing this (OpenJPA is used):
// ...
Dept dept = new Dept();
dept.addEmployee(new Employee(25));
this.em.persist(dept);
OpenJPA says:
Caused by: <openjpa-1.2.1-r752877:753278 nonfatal general error>
org.apache.openjpa.persistence.PersistenceException: Attempt to insert
null into a non-nullable column: column: ID table: EMPLOYEE in
statement [INSERT INTO employee (age) VALUES (?)]
{prepstmnt 841687127 INSERT INTO employee (age) VALUES (?)
[params=(int) 25]} [code=-10, state=23000]
Why ID is not auto-generated?
You one-to-many relationship is mapped as bidirectional (due to mappedBy), but I can't see the other side. Perhaps it's the cause.
And even if many-to-one side actually exists, it's not initialized in your code though it's an owning side of the relationship, therefore it specifies the state to be reflected in the database.
If you actually mean unidirectional relationship, you need to remove mappedBy.