I have a model where I implemented table per concrete class. So I have an abstract class having the common properties across multiple tables. and I have the following entities.
#Entity
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class BaseForm{
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
protected Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id=id;
}
#Column(name = "submission_date")
protected Date submissionDate;
public Date getSubmissionDate() {
return submissionDate;
}
public void setSubmissionDate(Date submissionDate) {
this.submissionDate=submissionDate;
}
}
#Entity
#Table(name = "form_a")
public class FormA extends BaseForm{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "formA", fetch = FetchType.LAZY)
#Cascade(value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
#Fetch(FetchMode.SUBSELECT)
#OrderBy("id")
protected List<UserForm> userForms = new ArrayList<UserForm>();
}
#Entity
#Table(name = "form_b")
public class FormB extends BaseForm{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "formB", fetch = FetchType.LAZY)
#Cascade(value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
#Fetch(FetchMode.SUBSELECT)
#OrderBy("id")
protected List<UserForm> userForms = new ArrayList<UserForm>();
}
#Entity
#Table(name = "user_form")
public class UserForm {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#ManyToOne(optional = true)
protected FormA formA;
#ManyToOne(optional = true)
protected FormB formB;
#ManyToOne(optional = true)
protected User user;
}
But whenever I try to use createAlias on the polymorphic query selecting all the forms joining userForms to return the user information for each form. It raises an exception.
2016-01-04 12:21:54,158 ERROR [org.hibernate.util.JDBCExceptionReporter] Not unique table/alias: 'userforms1_'
DetachedCriteria baseCR = DetachedCriteria.forClass(BaseForm.class);
baseCR.createAlias("userForms", "userForms");
);
baseCR.add(Restrictions.disjunction()
.add(Restrictions.isNotNull("userForms.formA"))
.add(Restrictions.isNotNull("userForms.formB"))
);
baseCR.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
baseCR.setProjection(Projections.rowCount());
List results= ht.findByCriteria(baseCR);
Here is the generated hibernate query
SELECT COUNT(*) AS y0_
FROM
(SELECT id,
submissionDate,
1 AS clazz_
FROM form_a
UNION
SELECT id,
submissionDate,
2 AS clazz_
FROM form_b
) this_
INNER JOIN user_form userforms1_
ON this_.id=userforms1_.formA_id
INNER JOIN user_form userforms1_
ON this_.id=userforms1_.formB_id
WHERE (userforms1_.formA IS NOT NULL
OR userforms1_.formB IS NOT NULL)
Any idea what would be the problem. and how to solve it?
You are accessing subclass fields with a superclass alias, which is conceptually wrong (although works in some situations, but obviously not always).
You should consider moving userForms to the base class or transforming your query to something like this (JPQL, just translate it to an equivalent Criteria):
select bf from BaseForm bf
where bf.id in (select fa.id from FormA fa join fa.userForms)
or bf.id in (select fb.id from FormB fb join fb.userForms)
Related
I have two entities with one to many relationships as below. Everything works fine except delete action. On deleting, I was getting ERROR: relation "a_b" does not exist. For that, I found the solution here.
According to an answer, there was an issue with the relationship and hibernate treats relationships as separate uni-directional relationships and it will create the third table a_b and tracks both sides of the relationship independently. To resolve the issue I had added mappedBy = "a".
Question is
Why does hibernate fires delete query for table a_b while it does not insert into a_b at the time new record creation?
Log on insert
Hibernate: insert into a...
Hibernate: insert into b...
Hibernate: insert into b...
Hibernate: insert into b...
**Why insert into a_b... is not generated/inserted?**
Log on delete
Hibernate: select a0_.id as id1_11_, from a a0_ where (a0_.id in (?))?
Hibernate: delete from b where a_id in (?)
Hibernate: delete from a_b where (a_id) in (select id from a where id in (?))
**Why delete from a_b if nothing is inserted into a_b**
12:19:50.432 [XNIO-1 task-20] WARN o.h.e.jdbc.spi.SqlExceptionHelper - SQL Error: 0, SQLState: 42P01
12:19:50.433 [XNIO-1 task-20] ERROR o.h.e.jdbc.spi.SqlExceptionHelper - ERROR: relation "a_b" does not exist
with cause = 'org.hibernate.exception.SQLGrammarException: could not execute statement' and exception = 'could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not execute statement'
Entity A
#Entity
#Table(name = "a")
public class A extends AbstractAuditingEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#OneToMany
private List<B> b;
.....
}
Entity B
#Entity
#Table(name = "b")
public class B extends AbstractAuditingEntity implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#ManyToOne
private A a;
.....
}
AServiceImpl
#Override
public int delete(List<Long> ids) {
...
bRepository.deleteWithIds(ids);
aRepository.deleteWithIds(ids);
}
BRepository
#Transactional
#Modifying
#Query("delete from b x where x.a.id in :ids")
void deleteLogsWithIds(#Param("ids") List<Long> ids);
ARepository
#Modifying
#Transactional
#Query("delete from a x where x.id in :ids")
void deleteJobWithIds(#Param("ids") List<Long> ids);
Current Code
Entity A
#Entity
#Table(name = "a")
public class A extends AbstractAuditingEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#OneToMany(mappedBy = "a")
private List<B> b;
.....
}
Entity B
#Entity
#Table(name = "b")
public class B extends AbstractAuditingEntity implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#ManyToOne
private A a;
.....
}
EDIT: Insert sequence
Save Entity A
aRepository.saveAndFlush(a);
Make a call to third party API and based on response set Entity A
for saving Entity B
x.forEach(b-> {
b.setA(aRepository.findById(aId).get());
bRepository.save(b);
});
There can be many scenarios to consider
If you are using a uni-directional oneToMany mapping it will require a join table to save the relationship.Since, a single A entity is associated with multiple B entities and due to its unidirectional nature it does not has a mapping column in B table.enter code here
#Entity
#Table(name = "A")
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private int id;
private String stateName;
//This is uni-directional since we donot have a corresponding reference to A in B entity
#OneToMany(cascade = CascadeType.ALL)
List<B> bs = new ArrayList<>();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public List<B> getBs() {
return bs;
}
public void setBs(List<B> bs) {
this.bs = bs;
}
public String getStateName() {
return stateName;
}
public void setStateName(String stateName) {
this.stateName = stateName;
}
}
#Entity
#Table(name="B")
public class B {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="ID")
private int id;
private String districtName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDistrictName() {
return districtName;
}
public void setDistrictName(String districtName) {
this.districtName = districtName;
}
}
In the above case its uni-directional oneToMany and it will require a join-table.
If you save your entity like this
enter code here
A a= new A();
B b=new B();
B b1=new B();
List<B> bs=new ArrayList<>();
bs.add(b);
bs.add(b1);
aRepository.save(a);
This will save the relationship mapping in join table.
Case 2:- Now if you add the following in the B entity class it will create a foreign-key column to A table. This will be again a unidirection ManyToOne mapping.
enter code here
#ManyToOne()
A a;
If you the following
enter code here
A a =new A();
B b =new B();
b.setA(a);
B b1=new B();
b1.setA(a);
bRepository.save(b);
bRepository.save(b1);
This will not save the relationship in the join table instead it will use the foreign-key which is present in the table B column named A_ID.
Case 3 :- Bidirectional oneToMany
enter code here
#Entity
#Table(name = "A")
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private int id;
private String stateName;
#OneToMany(mappedBy = "a", cascade = CascadeType.ALL)
List<B> bs = new ArrayList<>();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public List<B> getBs() {
return bs;
}
public void setBs(List<B> bs) {
this.bs = bs;
}
public void addB(B b) {
b.setA(this);
bs.add(b);
}
public void removeB(B b) {
b.setA(null);
bs.remove(b);
}
public String getStateName() {
return stateName;
}
public void setStateName(String stateName) {
this.stateName = stateName;
}
}
#Entity
#Table(name = "B")
public class B {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private int id;
private String districtName;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "A_ID")
A a;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
public String getDistrictName() {
return districtName;
}
public void setDistrictName(String districtName) {
this.districtName = districtName;
}
}
The above entity mapping is bi-directional oneToMany and doesn't uses the join-table.
My entity of product looks like below:
#Entity
#Table(name = "order")
public class OrderEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "order_id")
private Long id;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "order_products",
joinColumns = #JoinColumn(name = "order_id", referencedColumnName = "order_id"),
inverseJoinColumns = #JoinColumn(name = "product_id", referencedColumnName = "id")
)
private Set<ProductEntity> products = new HashSet<>();
}
ProductEntity:
#Entity
#Table(name = "product")
public class ProductEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(unique = true)
private String name;
#ManyToMany(mappedBy = "products")
private Set<OrderEntity> orders = new HashSet<>();
}
I want to get all orders where product name is equal to wanted value. And I write sql query to get result from database, but I cannot write hibernate query for Spring Data JPA.
My query for postgreSQL looks like this:
SELECT o.order_id, op.product_id, p.name
FROM public.order o
INNER JOIN public.order_products op
ON p.order_id = op.product_id
INNER JOIN public.product p
ON op.product_id = p.id
WHERE p.name = 'Foo';
And this query return me an id of order, product_id and name of product. And this works. But I didn't know how to write this question as spring query using #Query.
I need a metohod in my repository:
#Repository
public interface OrderRepository extends JpaRepository<OrderEntity, Long> {
#Query("") <- place for my query in Hibernate sql
List<OrderEntity> findAllByProductName(#Param("name") String name);
}
try this: (it returns full OrderEntity objects )
#Query("select o from OrderEntity o join o.products prod where prod.name = :name")
List<OrderEntity> findAllByProductName(#Param("name") String name);
if you need fetch eager all data for products use : ....OrderEntity o join o.products... in query instead of OrderEntity o join o.products
This is a projection consisting of columns from many entties, so you would have to go for the Result Class strategy.
Basically, you create a POJO class with expected result fields an an equivalent constructor:
public class ResultClass{
private Integer orderId;
private Integer productId;
private String name;
public ResultClass(Integer orderId, Integer productId, String name){
// set the fields
}
}
Then you alter the query a bit:
SELECT new com.mypkg.ResultClass(o.order_id, op.product_id, p.name)
FROM public.order o
INNER JOIN public.order_products op
ON p.order_id = op.product_id
INNER JOIN public.product p
ON op.product_id = p.id
WHERE p.name = 'Foo';
And change the return type on the interface method:
#Repository
public interface OrderRepository extends JpaRepository<OrderEntity, Long> {
#Query("...")
List<ResultClass> findAllByProductName(#Param("name") String name);
}
I'm trying to get Hibernate #OneToOne annotation working with 2 classes, Hito and Portada. Portada table has the foreign key of Hito, an int attribute called hito.
My entities looks like this:
Hito:
#Entity
#Table(name = "hito")
public class Hito implements Serializable {
//...other attributes
private Portada portada;
//...getters and setters from other attributes
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "hito")
public Portada getPortada(){ return portada;}
public void setPortada(Portada portada){ this.portada = portada;}
}
Portada:
#Entity
#Table(name = "portada")
public class Portada {
//...other attributes
private Hito hito;
//...getters and setters from other attributes
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "hito")
public Hito getHito() {return hito;}
public void setHito(Hito hito) {this.hito = hito;}
}
When I call hito.getPortada(), I expect a Portada object, but it returns null.
Any suggestions?
I tried to reproduce your problem with code:
#MappedSuperclass
public abstract class BaseEntity {
#Id #GeneratedValue
private Long id;
#Version
private long version;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
}
#Entity
#Table(name = "portada")
public class Portada extends BaseEntity {
//...other attributes
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "hito")
private Hito hito;
//...getters and setters from other attributes
public Hito getHito() {return hito;}
public void setHito(Hito hito) {this.hito = hito;}
}
#Entity
#Table(name = "hito")
public class Hito extends BaseEntity implements Serializable {
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "hito")
private Portada portada;
public Portada getPortada(){ return portada;}
public void setPortada(Portada portada){ this.portada = portada;}
}
// app:
Portada p = new Portada();
Hito h = new Hito();
p.setHito(h);
h.setPortada(p);
entityManager.persist(h);
entityManager.flush();
entityManager.clear();
Hito h2 = entityManager.find(Hito.class, h.getId());
System.out.println(h2.getPortada().toString());
tx.commit();
The last find generated sql:
select
hito0_.id as id1_4_0_,
hito0_.version as version2_4_0_,
portada1_.id as id1_7_1_,
portada1_.version as version2_7_1_,
portada1_.hito as hito3_7_1_
from
hito hito0_
left outer join
portada portada1_
on hito0_.id=portada1_.hito
where
hito0_.id=?
Everything worked for me...
EDIT: Only difference is that I like to put mapping attributes on fields instead of properties but it doesn't matter in this problem. Please check if you add both of your classes to persistance.xml or hibernate config.
I'm using JPA / EclipseLink 2.5.2 and want to add an additional where clause to my ManyToMany Mapping. The SQL Code would look like this:
SELECT * FROM Friends f
INNER JOIN User u ON f.friend_id = u.id
WHERE f.verified=1;
So I managed to do the JoinMapping with:
#Entity
#NamedQueries({
#NamedQuery(name="User.findAll", query="SELECT u FROM User u"),
#NamedQuery(name="User.login", query="SELECT a FROM User a WHERE a.username = :name and a.password = :password"),
#NamedQuery(name="User.findId", query="SELECT a FROM User a WHERE a.id = :id"),
#NamedQuery(name="User.findName", query="SELECT a FROM User a WHERE a.username = :name")
})
public class User implements Serializable {
...
#ManyToMany
#JoinTable(
name="Friends"
, joinColumns={
#JoinColumn(name="user_id")
}
, inverseJoinColumns={
#JoinColumn(name="friend_id")
}
)
List<User> friends;
}
But I dont know how to add the WHERE f.verified=1
How can this be realized?
Thanks in advance,
Cassidy
As advised in my comment you cannot use #ManyToMany as you require an additional column in the join table to indicate whether verified or not.
You then need to use a #OneToMany with an additional Entity, say Friendship. We can use the verified column as a discriminator and use a simple class hierarchy to distinguish between Unconfirmed and Confirmed friends.
This will then look something like the below (haven't tested it fully).
Note, I tested this with Hibernate but there is an issue so will need to look at again. These posts suggests the issue may be Hibernate specific:
https://stackoverflow.com/a/1631055/1356423.
Why #OneToMany does not work with inheritance in Hibernate
So may be worth trying with EclipseLink.
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#OneToMany(mappedBy = "user")
private Set<ConfirmedFriendship> confirmedFriendships;
#OneToMany(mappedBy = "user")
private Set<UnconfirmedFriendship> unconfirmedFriendships;
public List<User> getConfirmedFriends() {
return getFriends(confirmedFriendships);
}
public List<User> getUnconfirmedFriends() {
return getFriends(unconfirmedFriendships);
}
private List<User> getFriends(Set<? extends Friendship> friendships){
List<User> friends = new ArrayList<User>();
for(Friendship friendship : friendships) {
friends.add(friendship.getFriend());
}
return friends;
}
}
Base Entity for Friendship:
#Entity
#Table(name = "friendships")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "verified")
public abstract class Friendship {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "friend_id")
private User friend;
#Column(name = "verified")
private boolean verified;
public int getId() {
return id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public User getFriend() {
return friend;
}
public void setFriend(User friend) {
this.friend = friend;
}
public boolean isVerified() {
return verified;
}
public void setVerified(boolean verified) {
this.verified = verified;
}
}
Two subclassses which use the verified column as discriminator:
#Entity
#DiscriminatorValue(value = "1")
public class ConfirmedFriendship extends Friendship {
}
#Entity
#DiscriminatorValue(value = "0")
public class UnconfirmedFriendship extends Friendship {
}
I dont think there is JPA standard to include the "Where" option.
Personally I use NamedQuery to retrieve the entities. You can also use CriteriaQuery.
Well, In eclipseLink, you can use #AdditionalCriteria("this.verified=1")
I have this entity:
#Entity
#Table(name = "GDO.NEWS")
public class News implements Serializable, GenericType {
#Id
#Column(name="ID", insertable=true, updatable=false)
protected Integer id;
#OneToMany(fetch=FetchType.LAZY)
#JoinTable( name = "GDO.NEWS_FILTRO" , joinColumns = #JoinColumn(name = "ID_NEWS") ,
inverseJoinColumns = #JoinColumn(name= "ID_FILTRO", referencedColumnName = "ID_FILTRO") )
private Set<FiltroRegione> filtroRegione;
}
With a OneToMany relation to this entity:
#Entity
public class FiltroRegione extends AbstractFiltro {
//... does not matter
}
that extends this class:
#Entity
#Table(name = "GDO.FILTRO")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "CLASSE_FILTRO", discriminatorType=DiscriminatorType.STRING)
public class AbstractFiltro implements java.io.Serializable, GenericType {
private static final long serialVersionUID = 1L;
#Id
#Column(name="ID_FILTRO", insertable=true, updatable=false)
private Integer id;
#Column(name="TIPO_FILTRO")
protected String tipoFiltro;
public AbstractFiltro() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
The point is the #OneToMany association in News entity returns not only the FiltroRegione entities, but entities of all the subclasses of AbstractFiltro (they share the same database table GDO.FILTRO, but there is a discriminator column).
I think the problem is the #JoinTable, it ignores the discriminator column and just fetch all the records from the table GDO.FILTRO if there is a record in the join table GDO.NEWS_FILTRO associating a record from GDO.NEWS with a record from GDO.FILTRO.
Obviously I would like my Set< FiltroRegione > being populated only by FiltroRegione entities, entities stored in GDO.FILTRO with "FiltroRegione" as value of the discriminator column. How can i obtain such behaviour?
I am forced to use OpenJpa 1.x, supporting JPA 1.0 specs