I'm trying to implement a custom #loader using a namedQuery on a OneToOne - Relation of an entity.
However the lastDatalog field remains null at all given times
I've tested the named query befor on a simple integration test using a repositry, the result was exactly what I intend to have in the lastDestinationStatus
(I need the last updated record from the logs for this data and IREF combination)
when I query the Datalog entity with the id of the data I get the correct result so the Datalog entity seems to be persisted
maybe good to know : curent hibernate version on the project is 4.2.11.Final
this is en extract from entity 1
#Entity
#Table(name = "data")
#NamedQueries({
#NamedQuery(name = "LastLogQuery", query = "select log from DataLog log where log.data.id= ?1 and " +
"log.IREF = (select max(log2.IREF) from DataLog log2 where log2.data = log.data ) " +
"and log.tsUpdate = (select max(log3.tsUpdate) from DataLog log3 where log3.data = log.data and log3.IREF = log.IREF)")})
public class Data{
....
#OneToOne(targetEntity = DataLog.class)
#Loader(namedQuery = "LastLogQuery")
private DataLog lastDataLog;
}
extract from entity 2
#Entity
#Table(name ="log")
public class DataLog{
.......
#ManyToOne(fetch = FetchType.EAGER)
#org.hibernate.annotations.Fetch(value = org.hibernate.annotations.FetchMode.SELECT)
#JoinColumn(name = "DTA_IDN", nullable = false)
private Data data;
/** IREF */
#Column(name = "DSE_LOG_UID_FIL_REF_COD")
private String IREF;
#Column(name = "LST_UPD_TMS", nullable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date tsUpdate;
}
Related
Hello This is my 2 tables:
record and submission.
In submission, it has 1 composite primary key:(submission_id, question_id). One submission number can have several questions number. For example:
And as for record, it has a composite primary key:(student_id, exam_id). It looks like this:
I want to join these 2 tables like MySQL:
select * from record
left join submission
on record.submission_id = submission.submission_id.
But in hibernate, I have successfully join these 2 tables, but it gives me the following hql:
Hibernate:
select
...all columns...
from
record record0_
inner join
submission submission1_
on record0_.submission_id=submission1_.submission_id
and record0_.question_id=submission1_.question_id
where
1=1
In this case, I will get 0 rows in the result.
I don't want it use "and record0_.question_id=submission1_.question_id" after on clause, because there is no question_id in my record table.
But I have to add all primary keys into the #joinColumns() when I add Submission attribute in Record class, like this:
// Record class
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "record")
public class Record implements java.io.Serializable{
private static final long serialVersionUID = 1L;
// Other columns I don't need to show
#Column(name = "submission_id")
private Integer submissionId;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "submission_id", referencedColumnName = "submission_id",insertable=false, updatable=false),
#JoinColumn(name = "question_id", referencedColumnName = "question_id",insertable=false, updatable=false)
})
private Submission submission;
}
My Submission class like this:
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "submission")
public class Submission implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "submission_id")
private Integer submissionId;
#Id
#Column(name = "question_id")
private Integer questionId;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "submission")
private Record record;
}
Anyone can give me some advice?
-------- How I combine these tables-------
Actually, I join 4 tables and all these joins have the same problem declared above.
Code below is how i combine these 4 tables (record, submission, question, optional)
#Override
public List<RcdSubQuesOpt> getRcdSubQuesOpt(int studentID, int examId) {
Session session = this.getSession();
// RcdSubQuesOpt --> this is a class to store attributes from different tables(classes)
List<RcdSubQuesOpt> results;
Transaction transaction = null;
transaction = session.beginTransaction();
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery<RcdSubQuesOpt> criteriaQuery = criteriaBuilder.createQuery(RcdSubQuesOpt.class);
// To combine these tables use join
Root<Record> pRoot = criteriaQuery.from(Record.class);
Join<Record, Submission> rcd2sub = pRoot.join(Record_.submission);
Join<Submission, Question> sub2que = rcd2sub.join(Submission_.question);
Join<Question, Optional> que2opt = sub2que.join(Question_.optional);
// Attributes in RcdSubQuesOpt class
// get these columns from result and assign them to RcdSubQuesOpt class
criteriaQuery.multiselect(
pRoot.get("studentId"),
pRoot.get("examId"),
rcd2sub.get("questionId"),
rcd2sub.get("stuAnswer"),
sub2que.get("content"),
que2opt.get("content"),
que2opt.get("answer"));
// Predicate predicate = pRoot.get("examId").equals(1);
criteriaQuery.where();
results = session.createQuery(criteriaQuery).getResultList();
transaction.commit();
return results;
}
You haven't mentioned how you retrieve that data using hibernate. Have you tried trying to use #Query (select r from Record left join Submission sub on r.submissionId = sub.id where ...") ?
you have defined a #OneToOne relation in your record class. Apparantly thats wrong, since there exists more then one entry in your submission table for one record. So change this to #OneToMany and the respective relation in the submission class to #ManyToOne.
Besides your entities are not well named and mapped. Submission is in fact more of a question or an answer to it, because a line in that table does not represent one submission, which would be the expected meaning.
I have 2 entities CallRecords and CallRecordOperators with one-to-many relation as given below
public class CallRecords {
#Id
#Column(name = "id", unique = true)
private String id;
#Column(columnDefinition = "varchar(255) default ''")
private String callerNumber = "";
#OneToMany(mappedBy="callrecord")
private List<CallRecordOperators> callRecordOperators = new ArrayList<CallRecordOperators>();
//getter setters
}
public class CallRecordOperators {
#Id
#Column(name = "id", length = 50, unique = true, nullable = false, insertable = false, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "callRecordId")
private CallRecords callrecord;
#ManyToOne
#JoinColumn(name = "operatorId")
private Operator operator;
#Formats.DateTime(pattern = "yyyy-MM-dd HH:mm:yy")
#Column(columnDefinition = "TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP")
private Date startTime = new Date();
#Column(columnDefinition = "varchar(100) default ''")
private String dialStatus;
//getter setter
}
So if the user ask for all "CallRecords" data I also have to give "CallRecordOperators" as they are related.
Current code for Mapper and DTOs
#Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface CallRecordsMapper {
CallRecordsMapper INSTANCE = Mappers.getMapper(CallRecordsMapper.class);
#Mapping(source="callRecordOperators",target = "operators")
CallRecordsDto callRecordsToCallRecordsDto(CallRecords callRecords);
public abstract CallRecordOperatorsDto toTarget(CallRecordOperators source);
List<CallRecordsDto> callRecordsToCallRecordsDtos(List<CallRecords> callRecords);
}
public class CallRecordsDto {
private String callerNumber;
private List<CallRecordOperatorsDto> operators;
//getter setters
}
public class CallRecordOperatorsDto {
private String callRecordsId;
private String operatorId;
private String operatorName;
private String currentTime;
// getter setter
}
But for above code I am getting
{
"callerNumber": "9898989898",
"operators": [{
"callRecordsId": null,
"operatorId": null,
"operatorName": null,
"currentTime": null
}, {
"callRecordsId": null,
"operatorId": null,
"operatorName": null,
"currentTime": null
}]
}
the values of operator array are null. what could be he issue?
It seems your are lacking the mappings from CallRecordOperators to CallRecordOperatorsDto:
#Mapper
public interface CallRecordsMapper {
CallRecordsMapper INSTANCE = Mappers.getMapper(CallRecordsMapper.class);
#Mapping(source="callRecordOperators",target = "operators")
CallRecordsDto callRecordsToCallRecordsDto(CallRecords callRecords);
#Mapping(target = "callRecordsId", source = "callrecord.id")
#Mapping(target = "operatorId", source = "operator.id")
#Mapping(target = "operatorName", source = "operator.name")
#Mapping(target = "currentTime", source = "startTime")
CallRecordOperatorsDto callRecordOperatorsToDto(CallRecordOperators source);
}
When you do a Hibernate query of A elements, you can fetch the related B elements of the bs collection using different strategies. Some of them are:
If you use HQL to construct your queries, you can do a JOIN FETCH or LEFT JOIN FETCH to populate the bs collection:
String hql = "SELECT DISTINCT a FROM " + A.class.getName()
+ " a LEFT JOIN FETCH a.bs WHERE ...";
This query will load all data using a single SQL query.
Use eager fetching of the bs collection, changing the #OneToMany annotation:
#OneToMany(fetch=FetchType.EAGER)
private List<B> bs;
In this case, when you run a query of A elements, a SQL query will be launched to retrieve the A data, and for each A object in the result, a SQL query will be executed to load the corresponding bs collection.
If you use Criteria to build the query, you can change the fetch mode of the bs collection in a way similar to the HQL JOIN FETCH:
Criteria c = session.createCriteria(A.class);
c.setFetchMode("bs", FetchMode.JOIN);
c.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
How about switching to a slightly different approach that also performs better? By using Blaze-Persistence Entity Views you can define your mapping directly on the DTO classes and apply that onto a query builder to generate efficient queries that perfectly fit your DTO structure.
#EntityView(CallRecords.class)
public interface CallRecordsDto {
// The id of the CallRecords entity
#JsonIgnore
#IdMapping("id") String getId();
String getCallerNumber();
#Mapping("callRecordOperators")
List<CallRecordOperatorsDto> getOperators();
}
#EntityView(CallRecordOperators.class)
public interface CallRecordOperatorsDto {
// The id of the CallRecordOperators entity
#JsonIgnore
#IdMapping("id") Long getId();
#Mapping("callrecord.id")
String getCallRecordId();
#Mapping("operator.id")
String getOperatorId();
#Mapping("operator.name")
String getOperatorName();
#Mapping("startTime")
String getCurrentTime();
// Whatever properties you want
}
See how you can map the entity attributes right in your DTOs? And here comes the code for querying
EntityManager entityManager = // jpa entity manager
CriteriaBuilderFactory cbf = // query builder factory from Blaze-Persistence
EntityViewManager evm = // manager that can apply entity views to query builders
CriteriaBuilder<User> builder = cbf.create(entityManager, CallRecords.class)
.where("callerNumber").eq("123456789");
List<CallRecordsDto> result = evm.applySetting(
builder,
EntityViewSetting.create(CallRecordsDto.class)
).getResultList();
Note that this will roughly generate the following optimized query
SELECT
c.id,
c.callerNumber,
o.callrecord.id,
o.id,
o.startTime,
op.id,
op.name
FROM CallRecords c
LEFT JOIN c.callRecordOperators o
LEFT JOIN o.operator op
WHERE c.callerNumber = :param_1
This is my Entity configuration
#Entity
#NamedQuery(name = "Payment.findByEmail", query = "SELECT p FROM Payment p JOIN p.additionalAuthData a " +
"WHERE KEY(a) = 'email' AND VALUE(a) = ?1 AND (p.paymentType = 4 OR p.paymentType = 10)")
public class Payment {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Column(name = "payment_type")
private Integer paymentType;
/** other properties, getters and setters */
#ElementCollection
#CollectionTable(name = "additional_auth_data")
#MapKeyJoinColumn(name = "id", referencedColumnName = "id")
#MapKeyColumn(name = "field")
#Column(name = "data_value")
private Map<String, String> additionalAuthData;
}
The NamedQuery findByEmail("test#example.com") generates the following SQL
select -- all fields ...
from payment payment0_ inner join additional_auth_data additional1_ on payment0_.id=additional1_.id
where
additional1_.field='email' and (select additional1_.data_value from additional_auth_data additional1_ where payment0_.id=additional1_.id)='test#example.com' and (payment0_.payment_type=4 or payment0_.payment_type=10)
which is wrong: it may work if you have only one row but it blows up otherwise. H2 complains Scalar subquery contains more than one row and PostgreSQL more than one row returned by a subquery used as an expression. In fact, query's where condition compares a scalar value ('test#example.com') with a subquery.
The correct SQL should be:
select -- all fields
from payment payment0_ inner join additional_auth_data additional1_ on payment0_.id=additional1_.id
where additional1_.field='payerEmail' and additional1_.data_value='test#example.com' and (payment0_.payment_type=4 or payment0_.payment_type=10)
Is the HSQL correct? Is there a way to instruct Hibernate to generates a clever, better SQL? Is this a Hibernate bug?
Note: Hibernate shipped with Spring Boot Starter 1.3.7.RELEASE
Edit:
Using an #Embeddable class
#ElementCollection
#JoinTable(name = "additional_auth_data", joinColumns = #JoinColumn(name = "id"))
#MapKeyColumn(name = "field")
#Column(name = "data_value")
private Set<AdditionalData> additionalAuthData;
#Embeddable
public static class AdditionalData {
#Column(name = "field", nullable = false)
private String field;
#Column(name = "data_value")
private String dataValue;
protected AdditionalData() {
}
public AdditionalData(String field, String dataValue) {
this.field = field;
this.dataValue = dataValue;
}
/** Getters, setters; equals and hashCode on "field" */
}
#NamedQuery(name = "Payment.findByEmail", query = "SELECT p FROM Payment p JOIN p.additionalAuthData a " +
"WHERE a.field = 'email' AND a.dataValue = ?1 AND (p.paymentType = 4 OR p.paymentType = 10)")
solves the problem, and the SQL is correct, but it looks just plain wrong, like shooting a fly with a bazooka...
It generates correct SQL without value().
Use just a=?1
But I would expect is should generate it simple also with it.
I am using JPA and Spring for my db tasks and I need to have a join query like below in the JPA Repo class
#Query("SELECT 1 as id, COUNT(bill) as bills, ba.resource, MAX(b.updatedAt) as latestdate FROM Bill b join b.billComp ba where ba.comp.comp = ?1 group by ba.resource")
public List<BillCalc> findByBills(Long comp);
My Entity class is as below
#Entity
public class BillCalc {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "latestdate", nullable = false)
private Date latestdate;
#Column(name = "bills", nullable = false)
private Long bills;
#Column(name = "resource", nullable = false)
private String resource;
I cannot create a table for this and can someone help me in getting the mapping to work? It gives me an error saying cannot cast from Object to BillCalc.
I tried #SubSelect but it does not take parameters
add a constructor to BillCalc.
BillCalc(Integer id, long bills, String resouce, Date latestdate) {...}
then use a Select new query:
SELECT new BillCalc(1, COUNT(bill), ba.resource, MAX(b.updatedAt))
FROM Bill b join b.billComp ba
WHERE ba.comp.comp = ?1 group by ba.resource")
#See Chapter 4.8.2 "Constructor Expressions in the SELECT Clause" in JSR-000220 Enterprise JavaBeans 3.0 Final Release (persistence)
I have 2 tables in database:
ads - represent user defined advertisements
ad_categories - represent categories for advertisements
every advertisement must belong to exactly one category, so in ads table I defined a foreign key pointing to ad_categories with ON UPDATE NO ACTION ON DELETE NO ACTION.
In my application, user must be able to delete any category, but if that category contains advertisements, they must be moved to another category before category is deleted.
em.getTransaction().begin();
// get currentNode
AdCategories currentNode = em.find(AdCategories.class, currentNodeId);
// get ads
List<Ads> resultList = em.createQuery("SELECT a from Ads a WHERE a.adCategoryId = :categoryId").setParameter("categoryId", currentNode).getResultList();
// get their new location
AdCategories newLocation = em.find(AdCategories.class, newLocationId);
// set their new location
for(Ads a: resultList)
a.setAdCategoryId(newLocation);
em.remove(currentNode);
em.getTransaction().commit();
I expected, that affected advertisements will have ad_category_id changed and then the empty category will be removed. But affected advertisements are deleted too!!
I enabled logging in EclipseLink to FINEST level and found out, that when transaction is commited, firstly, UPDATE query is sent to database, which changes ad_category_id for affected advertisements and then category is deleted, but delete is cascaed to advertisements! I dont understand why, because advertisements should have updated ad_category_ids before remove occours.
I know, one simple workaround is to call em.flush() before removing the category, but I dont think it is optimal solution. I think, I need to understand this behaviour.
I am using EclipseLink with NetBeans and PostgreSQL.
Table definitions:
AdCategories
#Entity
#Table(name = "ad_categories")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "AdCategories.findAll", query = "SELECT a FROM AdCategories a"),
#NamedQuery(name = "AdCategories.findById", query = "SELECT a FROM AdCategories a WHERE a.id = :id"),
#NamedQuery(name = "AdCategories.findByParentId", query = "SELECT a FROM AdCategories a WHERE a.parentId = :parentId"),
#NamedQuery(name = "AdCategories.findByCategoryOrder", query = "SELECT a FROM AdCategories a WHERE a.categoryOrder = :categoryOrder"),
#NamedQuery(name = "AdCategories.findByCategoryDepth", query = "SELECT a FROM AdCategories a WHERE a.categoryDepth = :categoryDepth"),
#NamedQuery(name = "AdCategories.findByName", query = "SELECT a FROM AdCategories a WHERE a.name = :name"),
#NamedQuery(name = "AdCategories.findByGrandParentId", query = "SELECT a FROM AdCategories a WHERE a.grandParentId = :grandParentId"),
#NamedQuery(name = "AdCategories.findByParentName", query = "SELECT a FROM AdCategories a WHERE a.parentName = :parentName"),
#NamedQuery(name = "AdCategories.findByGrandParentName", query = "SELECT a FROM AdCategories a WHERE a.grandParentName = :grandParentName")})
public class AdCategories implements Serializable {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "adCategoryId")
private Collection<Ads> adsCollection;
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#Column(name = "parent_id")
private int parentId;
#Basic(optional = false)
#Column(name = "category_order")
private short categoryOrder;
#Basic(optional = false)
#Column(name = "category_depth")
private short categoryDepth;
#Basic(optional = false)
#Column(name = "name")
private String name;
#Column(name = "grand_parent_id")
private Integer grandParentId;
#Column(name = "parent_name")
private String parentName;
#Column(name = "grand_parent_name")
private String grandParentName;
...
Ads
#Entity
#Table(name = "ads")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Ads.findAll", query = "SELECT a FROM Ads a"),
#NamedQuery(name = "Ads.findByAdId", query = "SELECT a FROM Ads a WHERE a.adId = :adId"),
#NamedQuery(name = "Ads.findByName", query = "SELECT a FROM Ads a WHERE a.name = :name"),
#NamedQuery(name = "Ads.findByDescriptionShort", query = "SELECT a FROM Ads a WHERE a.descriptionShort = :descriptionShort"),
#NamedQuery(name = "Ads.findByDescriptionLong", query = "SELECT a FROM Ads a WHERE a.descriptionLong = :descriptionLong")})
public class Ads implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "ad_id")
private Integer adId;
#Basic(optional = false)
#Column(name = "name")
private String name;
#Basic(optional = false)
#Column(name = "description_short")
private String descriptionShort;
#Basic(optional = false)
#Column(name = "description_long")
private String descriptionLong;
#JoinColumn(name = "ad_category_id", referencedColumnName = "id")
#ManyToOne(optional = false)
private AdCategories adCategoryId;
...
The problem here is that you defined a bi-directional relationship, which needs to be manually managed (The JPA provider will not do it for you). In your calling code, you break the link between ads and their category, from the point of view of the ads.
for(Ads a: resultList)
a.setAdCategoryId(newLocation);
But, your category is still holding on to a collection of ads that it believes its related too, and when you delete it, those ads get deleted as well (because of the CascadeType.ALL annotation). There are two ways you can go about fixing this.
Keep the bidirectional relationship
If you really need to, you can leave the relationship bidirectional, but then you would have to properly disassociate the relationship on both sides, when you want to break it. It's normal to manage the relationship entirely from the 'owning' side, so I would do something like this:
public class Ads implements Serializable {
public void setAdCategoryId(AdCategories category) {
this.category.removeAd(this);
this.category = category;
this.category.addAd(this);
}
}
Very rough pseudocode, you will need to flesh it out
Remove the birectional relationship
Does a category really need to maintain a list of all ads that use it? Conceptually, I don't think it should. The list will get very large over time, and you could always query for it dynamically instead of storing it with each category. But that's a decision you have to make from a business point of view.
If you declare a cascade of type REMOVE (or ALL) on the collection of ads in AdCategory, you tell JPA: when I call remove() on an AdCategory, also call remove() on all the ads in this collection. So that's what JPA does.
You have a bidirectional association, it's your responsibility to make sure both sides of the association are in a coherent state. So if you change the category of an ad, you should also remove this ad from the set of ads in its category, and you should also add the ad to its new category. It's not absolutely mandatory in all the cases, but in yours, it is.
Also, your naming is really bad. An instance of AdCategories is a single category. So the entity should be named AdCategory. Same for Ads, which should be named Ad. The field adCategoryId doesn't contain a category ID, but a category. It should be named adCategory of category and not adCategoryId. Why name the field adId? It's the ID in the class Ad, so it's already obviously the ID of an Ad. It should thus be named id. descriptionLong should be named longDescription. That might seem like details, but those are the details that make code look good and be readable.