I have hierarchy of classes, for example AbstractClass - Employee - Engineer (and many others subclasses), and I use single table per class hierarchy strategy.
AbstractClass has property case, in Employee I define #DiscriminatorFormula("case"), Engineer has some #DiscriminatorValue.
When I load all objects with HQL-query SELECT id FROM Employee, Hibernate makes this SQL-query:
select employee0_.id as col_0_0_ from public.tbl_employee employee0_ where employee0_.case in ('engineer', 'operator')
In other words, Hibernate adds redundant restrictoin with all discriminator values.
Is there a way to exclude this IN restriction from SQL?
There is some simplified mapping of AbstractClass and Employee:
#MappedSuperclass
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class AbstractClass {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_GEN")
#SequenceGenerator(name = "SEQ_GEN", sequenceName = "objectid_sequence", allocationSize = 100)
private long id;
#Column(name = "case", nullable = false)
private String case;
}
#Entity
#Table(name = "tbl_employee")
#DiscriminatorFormula("case")
public class Employee extends AbstractClass {
#Column(name = "name", nullable = false)
private String name = "";
}
Related
I have two entities called Appointement and Client, and I would like to get all the
appointements of a given client. I am struggling with the following error message when I try to do so.
2022-05-24 13:30:41.685 WARN 23252 --- [ XNIO-3 task-1]
.m.m.a.ExceptionHandlerExceptionResolver : Resolved
[org.springframework.dao.InvalidDataAccessApiUsageException: Parameter
value [1000] did not match expected type [ma.mycom.myapp.domain.Client
(n/a)]; nested exception is java.lang.IllegalArgumentException:
Parameter value [1000] did not match expected type
[ma.mycom.myapp.domain.Client (n/a)]]
Here are my entities:
Client.java
#Entity
#Table(name = "CLIENTS")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Client implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
//other attributes, getters, setters
}
Appointment.java
#Entity
#Table(name = "APPOINTMRNTS")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Appointment implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
//other attributes
#ManyToOne
#JoinColumn(name = "ID_CLIENT")
private Client client;
//getters, setters
}
Controller, and how I call the query:
List<Appointement> clientAppointements = appointementRepository.findAllByClient(idClient);
Here is the query used in AppointementRepository.java (I guess it's the source of the problem)
#Query(
name = "SELECT pe.* FROM APPOINTEMENTS pe INNER JOIN CLIENTS e ON pe.ID_CLIENT = e.ID WHERE e.id = ?1",
nativeQuery = true
)
List<Appointement> findAllByClient(Long idClient);
Your Appointment class does not have a field called "client id" of any sort, it is only aware of the Client entity it has.
In your JPA repository methods, you can only use the existing fields of an entity.
Two of the most standard ways you can fix this are:
1- Adding Appointment field to the Client side of the relationship to make it bidirectional (I recommend this personally). Your Client entity will look like this:
#Entity
#Table(name = "CLIENTS")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Client implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
#OneToMany(mappedBy = "client", fetch = FetchType.LAZY)
List<Appointment> appointments;
//other attributes, getters, setters
}
Then you can simply get appointments by fetching a Client object, then simply accessing its appointments field.
List<Appointement> clientAppointements = clientRepository.findClientByField(field)
.getAppointments();
or you can even get appointments of a client in repository methods:
// in your appointment repository
#Query(value = "SELECT c.appointments FROM Client c WHERE c.id = :cId")
List<Appointment> getAppointmentsById(Long cId);
2- If you don't want to make the relationship bidirectional, you should fetch the Client object before searching it with the Appointment repository methods.
// in your appointment repository
List<Appointment> findByClient(Client client);
// then you can fetch it anywhere
Client client = clientRepository.findById(cliendId);
List<Appointment> clientAppointments = appointmentRepository.findByClient(client);
I have a parent entity 'contracts' that has a one-to-one relation with another entity 'child-contract'. the interesting thing is that the mapping field ('contract_number')id not a primary key-foreign key but is rather a unique field in both the tables. Also it is possible for a contracts to not have any child contract altogether. With this configuration I have observed hibernate to generate 1 additional query every time a contracts does not have a child-contract. I filed this behavior very strange. Is there a way to stop these unnecessary query generation or have I got something wrong.
below is a piece of my code configuration.
#Data
#Entity
#Table(name = "contracts")
public class Contracts implements Serializable {
#Id
#JsonIgnore
#Column(name = "id")
private String id;
#JsonProperty("contract_number")
#Column(name = "contract_number")
private String contractNumber;
#OneToOne(fetch=FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JsonProperty("crm_contracts")
#JoinColumn(name = "contract_number", referencedColumnName = "contract_number")
private ChildContract childContract ;
}
#Data
#NoArgsConstructor
#Entity
#Table(name = "child_contract")
#BatchSize(size=1000)
public class ChildContract implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#JsonProperty("id")
#Column(name = "id")
private String id;
#JsonProperty("contract_number")
#Column(name = "contract_number")
private String contractNumber;
}
Please help.
Thank-you
You can use NamedEntityGraph to solve multiple query problem.
#NamedEntityGraph(name = "graph.Contracts.CRMContracts", attributeNodes = {
#NamedAttributeNode(value = "crmContract") })
Use this on your repository method as
#EntityGraph(value = "graph.Contracts.CRMContracts", type = EntityGraphType.FETCH)
// Your repo method in repository
#Entity
#Table(name = "parent");
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class Parent {
#Id
#SequenceGenerator(name = "ME_SEQ", sequenceName = "ME_SEQ", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ME_SEQ")
#Column(name = "PARENT_ID", columnDefinition = "NUMBER(38,0)", nullable = false, unique = true)
private Long id;
}
There is also a child entity (seperate table) which has a PK and FK that points to Parent ID.
#Entity
#Table(name = "child")
public class Child extends Parent {
#Id
#Column(name = "PARENT_ID")
private Long id;
}
Even though there is two separated tables, I get an error from Hibernate:
org.hibernate.mapping.UnionSubclass cannot be cast to
org.hibernate.mapping.RootClass
Is it not possible to have ID in the child class, even if it's a different table from the parent?
I got an object that inherit from another and i am trying to save (persist) it in my db. The problem is that when i try, i got this error :
javax.persistence.PersistenceException:
org.hibernate.exception.SQLGrammarException: could not extract ResultSet
I don't know what i did wrong.. Here is my classes :
#Entity
#Table(name = "calamar.derogation")
public class Derogation
{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "derogation_seq_gen")
#SequenceGenerator(name = "derogation_seq_gen", sequenceName = "calamar.derogation_id_seq",initialValue = 1, allocationSize = 1)
private int id;
#OneToMany(mappedBy="derogation", cascade = CascadeType.ALL, orphanRemoval=true, fetch = FetchType.EAGER)
#OrderBy("id")
private Set<DerogationFille> listDerogationFille;
[...]
}
#Entity
public abstract class DerogationFille{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
protected int id;
#ManyToOne
protected Derogation derogation;
[...]
}
#Entity
#Table(name = "calamar.derogationlinux")
public class DerogationLinux extends DerogationFille
{
private String chemin;
private String userFam;
private String serveur;
#Column(name="typederogation")
private String type;
[...]
}
I got the error on this lines :
entityManager.getTransaction().begin();
entityManager.persist(derogation);
EDIT 1 ##
I haven't looked at my correctly, i've got this error into it too, i think it is the main problem
ERROR: relation "hibernate_sequence" does not exist
Please annotate your entities like this
#Table(name = "derogation", schema = "calamar")
I have the following scheme: TableA1 and TableA2 exist in the database and each is represented by an entity bean. Since they are related, I created an abstract class (TableA, it's an entity but does not exist in the database) where both entities inherit from this class. In addition, TableA has a one-to-one relationship with TableB.
My objective is to query TableB and from there get information of TableA1 or TableA2 depending on the type.
TableA1 and TableA2 each has an id (each table generates automatically a sequential number, so you may have repetition).
In TableB I have two columns that combined represent the foreign key: type and id. Type = 1 means that id is in TableA1. Similarly with TableA2.
My problem is that I don't know how to define these two columns as an external foreign key.
This is what I've got:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#DiscriminatorColumn(name="type")
public abstract class TableA {
#Id
#Column(name = "type")
protected int type;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
protected int id;
#Column(name = "name")
private String name;
// Getters and setters
}
#Entity
#DiscriminatorValue("1")
#Table (name="tableA1")
public class TableA1 extends TableA {
#Column(name="col1")
private String col1;
// Getters and setters
}
#Entity
#DiscriminatorValue("2")
#Table (name="tableA2")
public class TableA2 extends TableA {
#Column(name="col2")
private String col2;
// Getters and setters
}
#Entity
#Table (name="tableB")
public class TableB {
#Id
#Column(name="someId")
private Integer someId;
#Column(name="type")
private int type;
#Column(name="id")
private Integer id;
#OneToOne(optional = false, cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "type"),
#JoinColumn(name = "id" )
})
private TableA tableA;
// Getters and setters
}
Update
Am I looking for the impossible? This is what I found:
Polymorphic relations to non-leaf classes in a table-per-class hierarchy have many limitations. When the concrete subclass is not known, the related object could be in any of the subclass tables, making joins through the relation impossible. This ambiguity also affects identity lookups and queries; these operations require multiple SQL SELECTs (one for each possible subclass), or a complex UNION.
Update 2
TableA1, TableA2 and TableB already exist in the database and have the following structure:
CREATE TABLE TableA1 (
surrogate_key int AUTO_INCREMENT,
some_char char(30),
PRIMARY KEY (surrogate_key)
);
CREATE TABLE TableA2 (
surrogate_key int AUTO_INCREMENT,
some_int int,
PRIMARY KEY (surrogate_key)
);
CREATE TABLE TableB (
surrogate_key int AUTO_INCREMENT,
type int, // if type=1, sk represents the surrogate_key of tableA1
// if type=2, sk represents the surrogate_key of tableA2
sk int,
description varchar(200),
PRIMARY KEY (surrogate_key)
);
Update answer:
Updated to match database
You can use getDiscriminatorValue() to access the DiscriminatorValue.
Define mappings like this:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.INTEGER)
public abstract class TableA implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
#Column(name = "surrogate_key")
protected int id;
#Id
#Column(name = "type")
protected int type;
// Constructors & getters/setters
#Transient
public String getDiscriminatorValue() {
DiscriminatorValue val = this.getClass().getAnnotation(DiscriminatorValue.class);
return val == null ? null : val.value();
}
}
#Entity
#DiscriminatorValue("1")
public class TableA1 extends TableA {
#Column(name = "some_char", length = 1)
private char someChar;
// Constructors & getters/setters & toString/equals
}
#Entity
#DiscriminatorValue("2")
public class TableA2 extends TableA {
#Column(name = "some_int")
private int someInt;
// Constructors & getters/setters & toString/equals
}
#Entity
public class TableB implements Serializable {
#Id
#GeneratedValue
#Column(name = "surrogate_key")
private int id;
#OneToOne
#Cascade(value = CascadeType.SAVE_UPDATE)
#JoinColumns({#JoinColumn(name = "sk", referencedColumnName = "surrogate_key"),
#JoinColumn(name = "type", referencedColumnName = "type")})
private TableA tableA;
#Column(name = "description", length = 200)
private String description;
// Constructors & getters/setters & toString/equals
}
and query like this:
newSession.createQuery("from TableB tb where tb.tableA.type=:type order by tb.id asc").setParameter("type", 1));