I have a join table looks like this.
EXCHANGE_RATE
------------------------------
ID BIGINT PK
SOURCE_CURRENCY_ID BIGINT FK
TARGET_CURRENCY_ID BIGINT FK
Mapped ExchangeRate.java
public class ExchangeRate {
protected ExchangeRage() {
this(null, null);
}
// for SELECT NEW
public ExchangeRate(Currency sourceCurrency, Currency targetCurrency) {
this.sourceCurrency = sourceCurrency;
this.targetCurrency = targetCurrency;
}
#Id private Long id;
#ManyToOne private Currency sourceCurrency;
#ManyToOne private Currency targetCurrency;
}
How can I list all existing and non existing ExchangeRates for given sourceCurrency?
For example,
There are three Currencies. A, B, and C.
There is already an ExhangeRate from A to B
With following method, if sourceCurrency is A,
public List<ExchangeRate> listExchangeable(Currency sourceCurrency) {
//
}
How can I get following list?
ExchangeRate {
id: 0
sourceCurrency: A
targetCurrency: B
},
ExchangeRate {
id: NULL
sourceCurrency: A
targetCurrency: C
}
Required Query
// get all exchange rates for a source currency
public List<ExchangeRate> listExchangeable(Currency sourceCurrency) {
Query qry = em.createQuery("select r from ExchangeRate r where
r.sourceCurrency = :sourceCurrency");
qry.setParameter("sourceCurrency", sourceCurrency);
return qry.getResultList();
}
for non existing ExchangeRates for given sourceCurrency
Query qry = em.createQuery("select r from ExchangeRate r where
r.sourceCurrency != :sourceCurrency");
Related
I have a complex Oracle query which for simplicity's sake looks like this;
SQL> SELECT d.id AS dept_id,
2 d.name AS dept_name,
3 e.id AS emp_id,
4 e.name AS emp_name,
5 e.dept_id AS emp_dept_id
6 FROM drs2_dept d, drs2_emp e
7 WHERE d.id = e.dept_id (+)
8 /
DEPT_ID DEPT_NAME EMP_ID EMP_NAME EMP_DEPT_ID
---------- ------------------- ---------- -------------- -----------
1 SALES 101 JOHN 1
1 SALES 102 JANE 1
2 ADMIN
My Department class is;
#SqlResultSetMapping(
name = "Department.employeeMapping",
classes = {
#ConstructorResult(
targetClass = Department.class,
columns = {
#ColumnResult(name = "DEPT_ID", type = Integer.class),
#ColumnResult(name = "DEPT_NAME")
}
),
#ConstructorResult(
targetClass = Employee.class,
columns = {
#ColumnResult(name = "EMP_ID", type = Integer.class),
#ColumnResult(name = "EMP_NAME"),
#ColumnResult(name = "EMP_DEPT_ID", type = Integer.class)
}
)
}
)
#NamedNativeQuery(
name = "Department.findAllEmployees",
query = "SELECT d.id AS dept_id, " +
" d.name AS dept_name, " +
" e.id AS emp_id, " +
" e.name AS emp_name " +
" e.dept_id AS emp_dept_id, " +
"FROM drs2_dept d, drs2_emp e " +
"WHERE d.id = e.dept_id (+)",
resultSetMapping = "Department.employeeMapping"
)
#Entity
public class Department {
#Id // JPA will not start without it.
Integer id;
String name;
#OneToMany // JPA will not start without it.
List<Employee> employees = new ArrayList<>();
public Department(Integer id, String name) {
this.id = id;
this.name = name;
}
public Department() {}
// getters and setters
}
My Employee class is;
#Entity
public class Employee {
#Id Integer id;
Integer departmentId;
String name;
public Employee(Integer id, String name, Integer departmentId) {
this.id = id;
this.name = name;
this.departmentId = departmentId;
}
public Employee() {}
// getters and setters
}
Because I am using #ConstructorResult I am able to get the data, but it still in a flat structure, that is to say a List<Object[]> with three entries, each containing [Department, Employee]. So I have to do the following to move the Employee records within their respective Department;
#Component
public class DepartmentDAO {
#PersistenceContext EntityManager entityManager;
public Collection<Department> getAllDepartments() {
Query query = entityManager.createNamedQuery("Department.findAllEmployees");
Map<Integer, Department> map = new HashMap<>();
List<Object[]> list = query.getResultList();
for (Object[] tuple: list) {
Department d = (Department) tuple[0];
if (! map.containsKey(d.getId())) {
map.put(d.getId(), d);
}
d = map.get(d.getId());
Employee e = (Employee) tuple[1];
if (e.getId() != null) {
d.getEmployees().add(e);
}
}
return map.values();
}
}
Whenever I add any additional properties to the #OneToMany I seem to get spurious SQL generated in the Hibernate logs which is incorrect (i.e. non-existent column or table names), but as I stated at the start of this question, I want the native SQL only - I don't want Hibernate to figure out what I am trying to do.
Is there any way to get JPA/Hibernate to put the Employee objects into the Department's list for me?
(
As a sub-note, I have seen this question asked here, but either never answered or answered back in 2011, by which time JPA and Hibernate may have progressed.
I should also add that elsewhere in my project I already have Department and Employee fully mapped for CrudRepository use with #Table and #Column, however their #OneToMany definitions do not depict what I am doing in the above query, hence their absence in my example code.
)
This query does not have any clause that forces it to be implemented with a native.
In fact it is considered a bad practice.
Try this:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Departament> cq = cb.createQuery(Departament.class);
Root<Departament> rootDepartament = cq.from(Departament.class);
Join<Departament,Employee> joinEmployee = rootDepartament.join(Departament_.employees,JoinType.Left);
cq.select(rootDepartament);
List<Departament> result = entityManager.createQuery(cq).getResultList();
Native SQL with aliased field names + remapping to receive managed entities is required for more complex queries with joined tables.
However, the mapping of the SQL aliases leads to an exception where the aliased fields cannot be found. Can anybody detect an error in the code below, or is SQLResultSetMapping broken? (The sample below is intentionally simple to allow quick checking)
RDBMS H2, DDL
create table A(
ID INTEGER DEFAULT NOT NULL AUTO_INCREMENT PRIMARY KEY,
VAL VARCHAR(10)
);
insert into A (val) values ('val1');
insert into A (val) values ('val2');
Java class
#Entity
#NamedNativeQuery(name = "queryall",
query="select ID as AID, val from A",
resultSetMapping = "mapping")
#SqlResultSetMapping(name = "mapping",
entities = #EntityResult(
entityClass = A.class,
fields = {#FieldResult(name = "ID", column = "AID")})
)
public class A implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Integer id;
#Column(name = "VAL")
private String val;
public A() {
}
public A(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getVal() {
return val;
}
public void setVal(String val) {
this.val = val;
}
#Override
public String toString() {
return "entities.A[ id=" + id +", val="+val+ " ]";
}
public static void main(String[] args) {
EntityManagerFactory entityManagerFactory =
Persistence.createEntityManagerFactory("JavaApplication6PU");
EntityManager em = entityManagerFactory.createEntityManager();
Query sqlQuery = em.createNamedQuery("queryall");
List list = sqlQuery.getResultList();
for (Iterator<A> iterator = list.iterator(); iterator.hasNext();) {
a = iterator.next();
System.out.println(String.format("entity %s, managed: %s", a, em.contains(a)));
}
}
}
Execution stops with exception:
[EL Warning]: 2018-01-12 21:45:42.748--UnitOfWork(1823014131)--Exception
[EclipseLink-6044] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd):
org.eclipse.persistence.exceptions.QueryException
Exception Description: The primary key read from the row [DatabaseRecord(
A.ID => null
A.VAL => val1)] during the execution of the query was detected to be null.
Primary keys must not contain null.
Query: ResultSetMappingQuery(name="queryall" referenceClass=A sql="select ID as AID, val from A")
This, in other words, means: No mapping has taken place -> aliased fields not found
The same when the mapping is announced in adhoc Queries.
Query sqlQuery = em.createNativeQuery("select ID as AID, val from A","mapping");
If resultClass is used instead of resultSetMapping and no SQL aliases exist, the output is as it should be. (This proves that there is no misspelling of fields or any other error)
#NamedNativeQuery(name = "queryall",
query="select ID, val from A",
resultClass = A.class)
Output:
entity entities.A[ id=1, val=val1 ], managed: true
entity entities.A[ id=2, val=val2 ], managed: true
CREATE OR REPLACE TYPE "RD_PHN_TY" AS OBJECT (
PHN_TYP_CD VARCHAR2(1) ,
PHN_NBR VARCHAR2(30) )
CREATE OR REPLACE TYPE "RD_PHN_NT" AS TABLE OF RD_PHN_TY;
CREATE TABLE RD_CUSTOMER (
CUSTOMER_ID NUMBER ,
FIRST_NAME VARCHAR2(20) ,
LAST_NAME VARCHAR2(20) ,
PHN_TXT RD_PHN_NT , --- nested table
OFC_PHN RD_PHN_TY , --- struct
CONSTRAINT PK_CUSTOMER PRIMARY KEY (CUSTOMER_ID) )
NESTED TABLE PHN_TXT STORE AS PHN_TAB;
I have two fields- ofcPhn(OFC_PHN) is of RD_PHN_TY(struct) and phnTxt(PHN_TXT) is an array of RD_PHN_TY(s). I am able to persist ofcPhn using StructConverter but not the phnTxt. My Customer entity and RdPhnTy object is shown below. Any help would be appreciated!
Customer Entity
#Entity
#StructConverters( {
#StructConverter(name = "phoneConverter", converter =
"com.pete.spotify.eclipselink.converter.PhoneTypeConverter")
})
public class Customer implements Serializable {
........
private List<Phn> phnTxt;
private Phn ofcPhn;
........
#Convert("phoneConverter")
#Column(name = "OFC_PHN", columnDefinition = "RD_PHN_TY")
public Phn getOfcPhn () {
return this.ofcPhn ;
}
public void setOfcPhn (Phn ofcPhn ) {
this.ofcPhn = ofcPhn ;
}
}
RdPhnTy
-------
public class RdPhnTy {
private String phnTypCd;
protected String phnNbr;
public RdPhnTy() {
}
#Column(name = "PHN_TYP_CD")
public String getPhnTypCd() {
return phnTypCd;
}
public void setPhnTypCd(String phnTypCd) {
this.phnTypCd = phnTypCd;
}
#Column(name = "PHN_NBR")
public String getPhnNbr() {
return phnNbr;
}
public void setPhnNbr(String phnNbr) {
this.phnNbr = phnNbr;
}
}
Simplified version of my problem is this: I have three entities (annotations also simplified):
#Entity
public class A
{
#Id
private int id;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
private Collection<B> bs;
}
#Entity
public class B
{
#Id
private int id;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
private Collection<C> cs;
}
#Entity
public class C
{
#Id
private short id;
private Object someProperty;
#Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj != null && getClass() == obj.getClass())
{
final C other = (C) obj;
return id == other.id;
}
return false;
}
#Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = 31 + id;
return result;
}
}
Now I need to persist these entities (to an empty DB). The entities are created as a result of parsing XML data, so it is not as simple as the following code, but in reality it is done like that (that is why I'm not reusing c1):
A a = new A;
a.setId(1);
Collection<B> bs = new ArrayList<B>();
B b1 = new B();
b1.setId(21);
Collection<C> cs1 = new ArrayList<C>();
C c1 = new C();
c1.setId(31);
c1.setOtherProperty(null);
cs1.add(c1);
b1.setCs(cs1);
B b2 = new B();
b2.setId(22);
Collection<C> cs2 = new ArrayList<C>();
C c2 = new C();
c2.setId(31); // notice this is the same id as c1
c2.setOtherProperty(null);
cs2.add(c2);
b2.setCs(cs2);
bs.add(b1);
bs.add(b2);
a.setBs(bs);
Now when I try to persist the newly created entity A via
entityManager.merge(a);
I get an exception
Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.C(ID)"; SQL statement:
INSERT INTO PUBLIC.C (ID, SOMEPROPERTY) VALUES (?, ?) ...
In the SQL log, I can see that OpenJPA (which is the provider I'm using) is trying to insert a row with id 31 to table C two times. However, I would expect that c1 and c2 objects are treated as equal, and just one row is inserted. How can I possibly achieve that?
I'm new to using JPQL/JPA and I somehow can't get this working:
public List<Bil> hentBiler(int kontor) {
List<Bil> biler = new ArrayList<Bil>();
TypedQuery<Bil> query = em.createQuery("SELECT o FROM Bil o WHERE o.kontornr = ?1", Bil.class);
query.setParameter(1, kontor);
}
The kontornr column in the database is an integer.
The error i recieve is this:
Exception Description: Problem compiling [SELECT o FROM Bil o WHERE o.kontornr = ?1].
[26, 36] The state field path 'o.kontornr' cannot be resolved to a valid type.
#Entity
public class Bil {
#Id
private String regNr;
private String merke;
private String modell;
private String farge;
private char gruppe;
private boolean ledig;
private int kontorNr;
The field is declared as
private int kontorNr;
The query is
SELECT o FROM Bil o WHERE o.kontornr = ?1
Java is case sensitive. kontronr != kontorNr.