I have an entity with different fields:
#Entity
public class TestEntity {
private int id;
private String name;
private String description;
#ElementCollection
private Map<String, String> parameter = new HashMap<>();
}
The resulting tables are the following:
TestEntity(id, name, description)
TestEntity_parameter(TestEntity_id, parameter, parameter_KEY)
Now I want to create a named query for this TestEntity that checks if there exists a parameter_KEY of value "test" and with a parameter :parameter.
I tried something like this:
select te from TestEntity te join TestEntity_parameter tep where tep.parameter_KEY = test AND tep.parameter = :parameter
But when I try to deploy, I get an error.
I'm relatively new to hibernate and java ee. Maybe my approach is wrong but I did not find anything how to access the fields of a map with a named query since it creates a new table for that map. So i thought that I need to join those tables.
Hope you guys can help me :)
Thanks a lot :)
Greetings
Simon
You could use the below query.
SELECT te FROM TestEntity te INNER JOIN te.parameter p WHERE KEY(p) = :YOUR_KEY
AND VALUE(p) = :YOUR_VALUE
Related
I cound't find any nice solution to get only selected data from the domain?
E.g I have class:
#Entity
public class Reservation {
// private Integer RESERVATION_ID;
// private Integer id;
private long code;
private Date date;
private Client reservationClient;
private WashType reservationWashType;
private Vehicle reservationVehicle;
private Wash reservationWash;
private Worker reservationWorkerPesel;
private Review reservationReview;
private ReservationReminder reservationReminder;
}
And have simple query repository
public interface ReservationRepository extends JpaRepository<Reservation, Long> {
Reservation findByCode(long code);
}
I'd like to take from that query the Reservation object but without data's from class like Review, Worker.
So it means my result should looks like:
a whole object of Reservation which includes:
code,date,Client reservationClient,WashType reservationWashType,Vehicle reservationVehicle,Wash reservationWash, ReservationReminder reservationReminder
Is it possible to exclude it in nice way? Or if not how can I manage it?
Yes, you can easily do that so long as Review and Worker are marked to be lazily loaded.
What I mean is:
#ManyToOne(fetch = FetchType.LAZY)
private Review review;
Hibernate won't attempt to load the Review association until you call #getReview().
For situations then where you want your Reservation with the Review, you would then just need to specify at query-time that you want the relationship join-fetched.
#Query("SELECT r FROM Reservation r JOIN FETCH r.review WHERE r.code = :code")
List<Reservation> findByCode(Long code);
Remember, if Review cannot be null, make sure that #ManyToOne has the optional=false attribute so that when the join gets generated, it uses an inner join rather than an outer join to avoid performance overhead.
Right now, I am using the method multiselect of CriteriaQuery to put some values from entity Termine in entity Task like this:
CriteriaBuilder builder = getEm().getCriteriaBuilder();
CriteriaQuery<Task> taskCriteria = builder.createQuery(Task.class);
Root<Termin> terminRoot = taskCriteria.from(Termin.class);
taskCriteria.multiselect(terminRoot.get("text"), terminRoot.get("empfaenger"), terminRoot.get("datVon"));
taskCriteria.where(builder.equal(terminRoot.get("empfaenger"), "000"));
List<Task> task = getEm().createQuery(taskCriteria).getResultList();
return task;
This is working fine, but now I am willing to gather the values text, empfaenger and datVon not only from the entity Termine but also from the entity Aufgabe, so that I will have a list of Tasks, that contains every Termin and Aufgabe which are having the same empfaenger.
Is it possible? If yes, how?
Thanks a lot in advance for your help!
I would derive both classes from task.
#Entity(name="Task")
#Inheritance(strategy = InheritanceType.JOINED)
#NamedQuery(name="Task.findAll", query="SELECT t FROM Task t")
public class Task {
#Id
Long id;
String text;
String empfaenger;
}
#Entity
public class Termin extends Task{
...
}
#Entity
public class Aufgabe extends Task{
...
}
And select them with a named query
List<Task> resultList = entityManager.createNamedQuery("Task.findAll",Task.class).getResultList();
or a criteria query with Task as Root.
This is the way I did to collect data from multiple entities (custom Select).
For example, multiple entities:
Root<InflowEntity> rootInflow = criteriaQuery.from(InflowEntity.class);
Root<OutflowEntity> rootOutflow = criteriaQuery.from(OutflowEntity.class);
You select the attributes you need from the above 2:
criteriaQuery.multiselect(rootInflow.get("inflowID"), rootInflow.get("name"),
rootOutflow.get("count"), rootOutflow.get("dateRange"));
Add the predicates (constraints) you need, for example:
Predicate[] predicates = new Predicate[2];
predicates[0] = criteriaBuilder.equal(rootInflow.get("uuid"), loginContext.getUuid());
predicates[1] = criteriaBuilder.equal(rootOutflow.get("uuid"), loginContext.getUuid());
Process the results:
criteriaQuery.where(predicates);
List<ResultsBean> results = session.createQuery(criteriaQuery).getResultList();
This Java bean (this is not the Hibernate entity), ResultsBean, stores the results. That is, it needs to have a constructor to accommodate the input the way the multiselect is arranged.
Given a ZIP-code-like hierarchical code/name schema.
For example:
code = 101010
Code:
100000 level 1 code (10....)
101000 level 2 code (..10..)
101010 level 3 code (....10)
Name (short name)
100000 - A
101000 - a
101010 - i
Name (FullQualifiedName)
100000 - A
101000 - A->a
101010 - A-a->i
EDIT
I wanna following code (JPA pseudo code), but CANNOT.
#Entity
public class CodeName{
// ....
String code; // 100101 levels = {100000, 100100, 100101}
String name; //
#HowToMapDirectedToNameOfCode('100000') // #SecondTable ?
String name1;
#HowToMapDirectedToNameOfCode('100100')
String name2;
#HowToMapDirectedToNameOfCode('100101')
String name3;
String getFullQualifiedName(){
return String.format("%s->%s->%s", name1, name2, name3);
}
// getter and setter
}
But it's relatively easier in native SQL:
SELECT (select p1.name from codename p1 where p1.code= concat( substring(p.code,1,2), "0000") ) province,
(select p2.name from codename p2 where p2.code= concat( substring(p.code,1,4), "00") ) city,
(select p3.name from codename p3 where p3.code=p.code) area
FROM codename p WHERE p.code = '100101';
So, I implements it as following snippet.
#Entity
public class CodeName{
// ....
String code; // 100000, 101000, 100101
String name; // province, city , area
#Transient
String name1; // mapping directly?
#Transient
String name2; // mapping directly?
#Transient
String name3; // mapping directly?
String getFullQualifiedName(){
return String.format("%s->%s->%s", name1, name2, name3);
}
// getter and setter
}
public interface CodeNameRepository extends CrudRepository<CodeName, Long>, CodeNameRepositoryCustom {
#Query(" FROM CodeName p " +
" WHERE p.code = CONCAT(SUBSTRING(?1, 1, 2), '0000') " +
" OR p.code = CONCAT(SUBSTRING(?1, 1, 4), '00') " +
" OR p.code = ?1")
List<CodeName> findAllLevelsByCode(String code);
}
#Component
public class CodeNameRepositoryImpl implements CodeNameRepositoryCustom {
#Autowired
private CodeNameRepository codeNameRepository ;
#Override
public CodeName CodeNamefindFullQualifiedNameByCode(String code) {
List<CodeName> codeNames= codeNameRepository .findAllLevelsByCode(code);
CodeName codeName;
// extra name1, name2, name3 from list,
// fill code, name, name1, name2, name3 to codeName and
return codeName;
}
}
But it have SO MANY limitations.
Most likely, I need getFullQualifiedName(), to display it on UI, but every time I must have an extra call to populate all names.
For each entity has CodeName as its children, no matter how deep the codeName is at, I MUST expand to the codeName and reload it with FQN.
Can we mapping all #Transient names directly by JPA?
You could technically model your code repository entity as follows:
public class CodeName {
#Id
#GeneratedValue(GenerationStrategy.AUTO)
#Column
private Long id;
#ManyToOne
private CodeName parent;
#OneToMany(mappedBy = "parent")
private List<CodeName> children;
#Column
private String name;
#Transient
public String getFullyQualifiedName() {
List<String> names = new ArrayList<>();
names.add(name);
CodeName theParent = parent;
while(theParent != null) {
names.add(theParent.getName());
theParent = theParent.parent;
}
Collections.reverse(names);
return StringUtils.join(names, "->");
}
}
Because the parent relationships will be fetched EAGERLY because they mapped as #ManyToOne, you can basically start at any child CodeName entity and traverse up it's parent/child relationship to the root. This basically allows the getFullyQualifiedName method to build the name for you at runtime.
If performance becomes a problem doing this, you can always datamine the names ahead of time in your entity as you described by adding a #Column private String fullyQualifiedName and make sure that field is inserted when you create your codes. Then the transient method I added to my the entity can be dropped since you're caching the names at data insertion.
It is possible to write a JPQL, which is equivalent to your SQL query. The only tricky part is to rewrite nested selects into cross joins, because nested selects are not supported by JPA and you need to join unrelated entities. On the other hand, functions CONCAT and SUBSTRING are supported by JPQL in the same way as in SQL. See the following JPQL query, which should give you the results as the SQL query in the question:
SELECT p1.name // province
, p2.name // city
, p.name // area
FROM CodeName p, CodeName p1, CodeName p2
WHERE p.code = '100101'
AND p1.code = concat( substring(p.code,1,2), "0000")
AND p2.code= concat( substring(p.code,1,4), "00")
The above query will give you 3 values in one row, which cannot be mapped into a single entity. The result of the query will therefore be a list of Object[] arrays. You may also add the original entity into the select clause: SELECT p1.name, p2.name, p.name, p FROM .... This way, you may later process the list of results and assign first three values into the transient fields of the entity:
Object[] rows = query.getResultList();
for (Object row : rows) {
CodeName c = (CodeName)row[3];
c.setName1((String)row[0]);
c.setName2((String)row[1]);
c.setName3((String)row[2]);
}
Given the following entity:
#Entity
#Table(name = "subscription")
public class Subscription implements Serializable {
private static final long serialVersionUID = 1L;
#ElementCollection
#CollectionTable(joinColumns= #JoinColumn(name="subscription"))
private Set<Code> mainCodes = new HashSet<>();
#ElementCollection
#CollectionTable(joinColumns= #JoinColumn(name="subscription"))
private Set<Code> otherCodes = new HashSet<>();
}
So a Subscription can have zero or more mainCodes or otherCodes it's interested in. I can get hold of the mainCode and otherCode of a certain object that passes by. The codes themselves are embeddables with only single String fields.
How do I create a JPA Query (or CriteriaBuilder) which searches in these collections with an "OR" mechanism?
So basically I'm looking for a query like this:
select s from subscription s where :myMainCode IN s.mainCodes OR :otherCode IN s.otherCodes
Is something like this doable with CriteriaBuilder or do I need to use a more explicit query? If so, what does the query look like?
EDIT: Tried this with CriteriaBuilder:
final CriteriaBuilder cb = this.entityManager.getCriteriaBuilder();
final CriteriaQuery<Subscription> cq = cb.createQuery(Subscription.class);
final Root<Subscription> root = cq.from(Subscription.class);
final Expression<Collection<Code>> mainCodes = root.get("mainCodes");
final Predicate containsMainCode = cb.isMember(obj.getClassCode(), mainCodes);
final Expression<Collection<Code>> otherCodes = root.get("otherCodes");
final Predicate containsOtherCode = cb.isMember(obj.getOtherCode(), otherCodes);
final Predicate searchPredicate = cb.or(containsMainCode, containsOtherCode);
cq.select(root).where(searchPredicate);
However, this creates an inner join of both collections involved, meaning that it will return no results if there is a row for mainCode, but not for otherCode in the database, it generates this query:
SELECT t0.ID
FROM Subscription_OTHERCODES t2, Subscription_MAINCODES t1, subscription t0
WHERE ((t1.CODESYSTEM = ?) AND (t1.CODE = ?)) OR ((t2.CODESYSTEM = ?) AND (t2.CODE = ?))) AND ((t1.subscription = t0.ID) AND (t2.subscription = t0.ID))
So even if it finds a matching mainCode, it fails if it doesn't have any otherCode.
It is other way around that in your example.
For example if the code has name property):
select s from Subscription s left join s.mainCodes m left join s.otherCodes o
where m.name IN :myMainCode or o.name IN :myOtherCode
I am trying the following which is resulting in an additional update execution and failing my tests.
I have an entity like this.
#Entity
#SqlResultSetMapping(name = "tempfilenameRSMapping",
entities = { #EntityResult(entityClass = MyEntity.class) },
columns = { #ColumnResult(name = "TEMPFILENAME") })
//The reason for this mapping is to fetch an additional field data through join.
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#Column(name="ID")
private String id;
#Column(name="NAME")
private String name;
#Column(name="DESC")
private String description;
#Column(name="STATUS")
private String status;
//follwed by getter setters
}
I am trying to do a retrieve with a native query. And for the retrieved entity, I execute a native update (the reason for native update is that I want to update just one single field). Note that I am not updating the retrieved entity directly.
What I observe is that my update is not getting executed properly. When I turn the TRACE on, I notice that on flush openJPA is executing an additional update query and therefore overriding my original update.
e.g.
SELECT M.ID, M.NAME, M.DESC, O.TEMPFILENAME FROM MY_TABLE M, OTHER_TABLE O WHERE M.ID = ?
UPDATE MY_TABLE SET STATUS = ? WHERE ID = ?
UPDATE MY_TABLE SET ID=?, NAME=?, DESC=?, STATUS=? WHERE ID = ?
What can I do to skip the auto-updation?
Edit:
Here are the routines we use for executing the queries.
The following routine returns a named native query sql.
public String getNamedNativeQuerySql(EntityManagerFactory emf, String qryName) {
MetamodelImpl metamodel = (MetamodelImpl) emf.getMetamodel();
QueryMetaData queryMetaData =
metamodel.getConfiguration().getMetaDataRepositoryInstance().getQueryMetaData(null, qryName, null, true);
String queryString = queryMetaData.getQueryString();
return queryString;
}
The code for retrieval:
Query query = entityManager.createNamedQuery("retrieveQry");
query.setParameter(1, id);
Object[] result = (Object[]) query.getSingleResult();
MyEntity entity = (MyEntity) result[0];
String tempFileName = (String) result[1];
The code for update that follows retrieval:
Query qry = entityManager.createNamedQuery("updateQry");
qry.setParameter(1, status);
qry.setParameter(2, entity.getId() );
qry.executeUpdate()
Edit:
I see the problem even without the update statement. OpenJPA is
executing an additional update query even if I do a simple find.
The problem was with runtime enhancement. OpenJPA was unable to do a proper detection of dirty state with runtime-enhanced entities.
It got resolved by doing a build time enhancement.