This is a complex problem i'm gonna try describe step by step
what i'm trying to achieve is this query
SELECT FUNCTION(GetTaskStatus(t.due_date, t.completed_date)) as status, count(*) as total FROM tasks t GROUP BY status
where GetTaskStatus is a stored function
In my java code TaskStatus is an enum:
public enum TaskStatus {
COMPLETED(2), LATE_COMPLETE(14), SCHEDULED(3), DUE_TODAY(5), LATE(7), OTHER(11);
private final int code;
TaskStatus(int i) {
code = i;
}
#javax.persistence.Converter(autoApply = true)
public static final class Converter implements AttributeConverter<TaskStatus, Integer> {
#Override
public Integer convertToDatabaseColumn(TaskStatus attribute) {
if (attribute == null)
return null;
return attribute.code;
}
#Override
public TaskStatus convertToEntityAttribute(Integer dbData) {
return fromInteger(dbData);
}
public static TaskStatus fromInteger(Integer dbData) {
return dbData == null || dbData == 0 ? null : Arrays.stream(values()).filter(taskStatus -> taskStatus.code == dbData).findFirst().orElse(OTHER);
}
}
note that i've a converter for that so hibernate should be able to deserialize that.
then my dto
#Getter
#Setter
#ToString
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class TaskMetricsDto {
#JsonProperty("status")
private TaskStatus status;
#JsonProperty("total")
private int total;
public TaskMetricsDto(#Nullable Integer statusCode, #Nullable Long total) {
status = TaskStatus.Converter.fromInteger(statusCode);
if (total != null)
this.total = total.intValue();
}
public void setStatus(Integer integer) {
this.status = TaskStatus.Converter.fromInteger(integer);
}
public void setTotal(Long total) {
if (total != null)
this.total = total.intValue();
}
}
then we go repository
#Repository
public interface TaskRepository extends JpaRepository<Task, Long> {
#Query("SELECT new br.com.fisgar.crm.dtos.TaskMetricsDto(CAST(GetTaskStatus(t.dueDate, t.completedDate) as java.lang.Integer) as status, count(*)) FROM Task t WHERE t.board.id IN (:boardIds) GROUP BY status")
public List<TaskMetricsDto> findMetrics(#Param("boardIds") Set<Long> boardIds);
}
This query is working, but in my opnion is much verbose and overcomplicated compared to the original sql query... all this due many problems I had
PROBLEM 1:
if i do
#Query("SELECT CAST(GetTaskStatus(t.dueDate, t.completedDate) as java.lang.Integer) as status, count(*) FROM Task t WHERE t.board.id IN (:boardIds) GROUP BY status")
without the explicit new, the query fails
"message": "CONVERTERNOTFOUNDEXCEPTION: No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [br.com.fisgar.crm.dtos.TaskMetricsDto]",
but my dto has exact two fields and even has a constructor which takes an int and a long... even getter and setter for that...
according to spring manual this should work
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
PROBLEM 2:
altought i have an converter for TaskStatus - Integer, hiberna
Related
I am trying to use the JPA Criteria API to filter the results and aggregate them using simple count, min, avg and max. I am using Spring Boot 2.7.8, so I am trying to use Interface-projections such that these aggregated results look the same as the simpler queries done automatically by the Spring repositories.
My domain entity (simplified for brevity) looks like this:
#Entity
#Table(name = "vehicle_stopped")
#IdClass(VehicleStopped.VehicleStoppedPK.class)
public class VehicleStopped implements Serializable {
#Id
#Column(name = "stopped_session_uuid", nullable = false)
private String stoppedSessionUuid;
#Id
#Column(name = "start_ts", nullable = false)
private OffsetDateTime startTs;
#Column(name = "end_ts", nullable = false)
private OffsetDateTime endTs;
#Column(name = "duration_seconds")
private Double durationSeconds;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "zone_id")
private CameraZone cameraZone;
#Override
public VehicleStoppedPK getId() {
VehicleStopped.VehicleStoppedPK pk = new VehicleStopped.VehicleStoppedPK();
pk.setStartTs(this.getStartTs());
pk.setStoppedSessionUuid(this.getStoppedSessionUuid());
return pk;
}
public OffsetDateTime getEndTs() {
return endTs;
}
public void setEndTs(OffsetDateTime endTs) {
this.endTs = endTs;
}
public Double getDurationSeconds() {
return durationSeconds;
}
public void setDurationSeconds(Double durationSeconds) {
this.durationSeconds = durationSeconds;
}
public CameraZone getCameraZone() {
return cameraZone;
}
public void setCameraZone(CameraZone cameraZone) {
this.cameraZone = cameraZone;
}
public VehicleType getVehicleType() {
return vehicleType;
}
public void setVehicleType(VehicleType vehicleType) {
this.vehicleType = vehicleType;
}
public String getStoppedSessionUuid() {
return stoppedSessionUuid;
}
public void setStoppedSessionUuid(String stoppedSessionUuid) {
this.stoppedSessionUuid = stoppedSessionUuid;
}
//some details removed for brevity
#Override
public static class VehicleStoppedPK implements Serializable {
private OffsetDateTime startTs;
private String stoppedSessionUuid;
public VehicleStoppedPK() {
}
public OffsetDateTime getStartTs() {
return startTs;
}
public void setStartTs(OffsetDateTime startTs) {
this.startTs = startTs;
}
public String getStoppedSessionUuid() {
return stoppedSessionUuid;
}
public void setStoppedSessionUuid(String stoppedSessionUuid) {
this.stoppedSessionUuid = stoppedSessionUuid;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
VehicleStoppedPK that = (VehicleStoppedPK) o;
return Objects.equals(startTs, that.startTs) && Objects.equals(stoppedSessionUuid, that.stoppedSessionUuid);
}
#Override
public int hashCode() {
return Objects.hash(startTs, stoppedSessionUuid);
}
#Override
public String toString() {
return "VehicleStoppedPK{" +
"startTs=" + startTs +
", stoppedSessionUuid='" + stoppedSessionUuid + '\'' +
'}';
}
}
}
#Entity
#Table(name = "camera_zone")
public class CameraZone implements Serializable {
#Id
#SequenceGenerator(name = "camera_zone_id_seq", sequenceName = "camera_zone_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "camera_zone_id_seq")
#Column(name = "id", updatable=false)
private Integer id;
#Column(name = "uuid", unique = true)
private String uuid;
#Column(name = "type")
private String type;
#Column(name = "name")
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CameraZone that = (CameraZone) o;
return Objects.equals(id, that.id) && Objects.equals(uuid, that.uuid) && Objects.equals(camera, that.camera) && Objects.equals(type, that.type) && Objects.equals(name, that.name);
}
#Override
public int hashCode() {
return Objects.hash(id, uuid, camera, type, name);
}
}
The code that I have in my Repository implementation looks like this:
public class SpecificationVehicleStoppedRepositoryImpl
implements SpecificationVehicleStoppedRepository {
#Autowired private EntityManager em;
#Autowired ProjectionFactory projectionFactory;
#Override
public List<VehicleStoppedAggregate> getStoppedVehiclesCount(Specification<VehicleStopped> spec) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Tuple> query = builder.createTupleQuery();
Root<VehicleStopped> root = query.from(VehicleStopped.class);
Predicate predicate = spec.toPredicate(root, query, builder);
if (predicate != null) {
query.where(predicate);
}
Path<Number> duration = root.get("durationSeconds");
Path<CameraZone> zone = root.get("cameraZone");
query
.multiselect(zone,
builder.count(root).alias("totalVehicles"),
builder.min(duration).alias("minDuration"),
builder.avg(duration).alias("avgDuration"),
builder.max(duration).alias("maxDuration"))
.groupBy(zone);
List<Tuple> rawResultList = em.createQuery(query).getResultList();
return project(rawResultList, VehicleStoppedAggregate.class);
}
private <P> List<P> project(List<Tuple> results, Class<P> projectionClass) {
return results.stream()
.map(tuple -> {
Map<String, Object> mappedResult = new HashMap<>(tuple.getElements().size());
for (TupleElement<?> element : tuple.getElements()) {
String name = element.getAlias();
mappedResult.put(name, tuple.get(name));
}
return projectionFactory.createProjection(projectionClass, mappedResult);
})
.collect(Collectors.toList());
}
}
The interface-based projection I am trying to populate (using SpelAwareProxyProjectionFactory) is this:
public interface VehicleStoppedAggregate {
CameraZone getCameraZone();
Integer getTotalVehicles();
Double getMinDuration();
Double getAvgDuration();
Double getMaxDuration();
}
The call to getStoppedVehiclesCount() fails with the following error:
ERROR: column "camerazone1_.id" must appear in the GROUP BY clause or be used in an aggregate function
This error is coming from the PostgreSQL database, and rightly so because the SQL hibernate generates is incorrect:
select
vehiclesto0_.zone_id as col_0_0_,
count(*) as col_1_0_,
min(vehiclesto0_.duration_seconds) as col_2_0_,
avg(vehiclesto0_.duration_seconds) as col_3_0_,
max(vehiclesto0_.duration_seconds) as col_4_0_,
camerazone1_.id as id1_2_,
camerazone1_.name as name2_2_,
camerazone1_.type as type3_2_,
camerazone1_.uuid as uuid4_2_
from
vehicle_stopped vehiclesto0_
inner join
camera_zone camerazone1_
on vehiclesto0_.zone_id=camerazone1_.id cross
where
vehiclesto0_.start_ts>=?
and vehiclesto0_.start_ts<=?
and 1=1
and 1=1
and 1=1
group by
vehiclesto0_.zone_id
It is not grouping by the other fields it is requesting from the joined table.
If I had to use a normal class, instead of a Tuple, it would work, but it would mean I would have to create a class with a huge constructor for all fields for Hibernate to populate it.
Somehow, when I use Interface-based projections with Spring's repositories rather than my criteriaquery, the same scenario works. They manage to populate the one-to-many relationships just fine.
Is there a way to fix this and make Hibernate ask for the right fields?
I am using Hibernate 5.6.14.Final (as bundled with Spring Boot 2.7.8).
I believe the "solution" is two create two "independent" query roots and join them together:
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Tuple> query = builder.createTupleQuery();
Root<VehicleStopped> root = query.from(VehicleStopped.class);
// instead of Path<CameraZone> zone = root.get("cameraZone")
Root<CameraZone> zone = query.from(CameraZone.class);
query.where(builder.equal(zone, root.get("cameraZone")));
Path<Number> duration = root.get("durationSeconds");
query
.multiselect(zone,
builder.count(root).alias("totalVehicles"),
builder.min(duration).alias("minDuration"),
builder.avg(duration).alias("avgDuration"),
builder.max(duration).alias("maxDuration"))
.groupBy(zone);
session.createQuery(query).getResultList();
In that case Hibernate 5 produces following SQL (which actually looks weird from my perspective due to missing columns in group by clause):
select
naturalidc1_.id as col_0_0_,
count(*) as col_1_0_,
min(naturalidc0_.duration_seconds) as col_2_0_,
avg(naturalidc0_.duration_seconds) as col_3_0_,
max(naturalidc0_.duration_seconds) as col_4_0_,
naturalidc1_.id as id1_0_,
naturalidc1_.name as name2_0_,
naturalidc1_.type as type3_0_,
naturalidc1_.uuid as uuid4_0_
from
vehicle_stopped naturalidc0_ cross
join
camera_zone naturalidc1_
where
naturalidc1_.id=naturalidc0_.zone_id
group by
naturalidc1_.id
FYI. Your initial query does work in Hibernate 6 and produced SQL does look more correct but still weird:
select
c1_0.id,
c1_0.name,
c1_0.type,
c1_0.uuid,
count(*),
min(v1_0.duration_seconds),
avg(v1_0.duration_seconds),
max(v1_0.duration_seconds)
from
vehicle_stopped v1_0
join
camera_zone c1_0
on c1_0.id=v1_0.zone_id
group by
1,
2,
3,
4
I have a DTO interface which fetches data from different tables using joins. I have made a DTO interface with the abstract getter methods something like this.
public interface HRJobsDTO {
String getEditorName();
String getEditorId();
String getBillingMonth();
Integer getEditorWordCount();
Integer getJobCount();
Integer getEmployeeGrade();
Float getGrossPayableAmount();
Float getJobBillingRate();
Float getTaxDeduction();
Float getTaxDeductionAmount();
Float getNetPayableAmount();
String getInvoiceStatus();
String getFreelanceInvoiceId();
}
In this interface my getFreelanceInvoiceId(); method returns a JSON Array using json_arrayagg function of mysql. I changed the datatype to String, String[] and Arraylist but it returns something like this in my response
"freelanceInvoiceId": "[\"4af9e342-065b-4594-9f4f-a408d5db9819/2022121-95540\", \"4af9e342-065b-4594-9f4f-a408d5db9819/2022121-95540\", \"4af9e342-065b-4594-9f4f-a408d5db9819/20221215-53817\", \"4af9e342-065b-4594-9f4f-a408d5db9819/20221215-53817\", \"4af9e342-065b-4594-9f4f-a408d5db9819/20221215-53817\"]"
Is there any way to return only array with exclusion of backslashes?
You can use #Converter from JPA (implemented by hibernate also)
#Converter
public class List2StringConveter implements AttributeConverter<List<String>, String> {
#Override
public String convertToDatabaseColumn(List<String> attribute) {
if (attribute == null || attribute.isEmpty()) {
return "";
}
return StringUtils.join(attribute, ",");
}
#Override
public List<String> convertToEntityAttribute(String dbData) {
if (dbData == null || dbData.trim().length() == 0) {
return new ArrayList<String>();
}
String[] data = dbData.split(",");
return Arrays.asList(data);
}
}
And references it in the pojo class as below
#Column(name="freeLanceInvoiceId")
#Convert(converter = List2StringConveter.class)
private List<String> tags=new ArrayList<>();
Basically I have list of names separated by camma (;),
CREATE TABLE familynames (
id int4 NOT NULL DEFAULT nextval('family_id_seq'::regclass),
names varchar NULL
);
INSERT INTO familynames (id, names) VALUES(1, 'Animalia;Arthropoda;Pancrustacea;Hexapoda');
The converter :
#Converter
public class NameConverter implements AttributeConverter<List<String>, String> {
#Override
public List<String> convertToEntityAttribute(String attribute) {
if (attribute == null) {
return null;
}
List<String> namesList = new ArrayList<String>(Arrays.asList(attribute.split(";")));
return namesList;
}
#Override
public String convertToDatabaseColumn(List<String> namesList) {
if (namesList == null || namesList.size() == 0) {
return null;
}
String result = namesList.stream().collect( Collectors.joining( ";" ) );
return result;
}
}
An entity that map the sql table
#Entity
#Table(name = "familynames")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class FamilyNames implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Convert(converter = NameConverter.class, attributeName = "names")
private List<String> names;
}
An interface for projection
public interface FamilyNamesDto {
Integer getId();
List<String> getNames();
}
The repository that ensure the selection and the projection
#Repository
public interface FamilyNamesRepository extends JpaRepository<FamilyNames, Long> {
/** #return the whole elements of mapped elements as dto */
List<FamilyNamesDto> findAllProjectedBy();
}
How to autowire it
#Autowired
private FamilyNamesRepository familyNamesRepository;
how to call repository within any service
List<FamilyNamesDto> fs = familyNamesRepository.findAllProjectedBy();
results :
I am working on a project with GraphQL-java and Hibernate with MariaDB.
In my current solution, I get 18938 results back. I just want to see the last 10 of these. So I am looking for a solution to limit the number of results.
On the internet I see examples of limiting the number of results (https://graphql.org/learn/pagination/). They call it pagination. However, I cannot find the server implementation of this. Does anyone have experience with this?
I have an Entity class, with some properties : Test.java
#Entity
#Table(name = "test")
public class Test {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size(max = 64)
#Column(nullable = false)
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent")
private Test parent;
public Test() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Test getParent() {
return parent;
}
public void setParent(Test parent) {
this.parent = parent;
}
My repository class: TestRepository.java
public interface TestRepository extends CrudRepository<Test, Integer> {}
My GraphQL resolver class: Query.java
#Component
public class Query implements GraphQLQueryResolver {
private TestRepository testRepository;
#Autowired
public Query(TestRepository testRepository) {
this.testRepository = testRepository;
}
public Iterable<Test> findAllTests(Integer first) {
return testRepository.findAll();
}
public long countTests() {
return testRepository.count();
}
}
My GraphQL schema: test.graphqls
type Test {
id: ID!
name: String!
parent: Test
}
#extend query
type Query {
findAllTests(first: Int): [Test]!
countTests: Int!
}
To summarize my last comment here is what I would do:
Instead of extending CrudRepository, extend PagingAndSortingRepository (which is extending CrudRepository)
public interface TestRepository extends PagingAndSortingRepository<Test, Integer> {
}
In your Query class pass two args to findAllTests method, page and size that will be used to create the Pageable object
#Component
public class Query implements GraphQLQueryResolver {
// other properties & methods are omitted for brevity
public Iterable<Test> findAllTests(Integer page, Integer size) {
Pageable pageable = PageRequest.of(page, size);
return testRepository.findAll(pageable).getContent(); // findAll returns Page and we can get the underlying List with getContent
}
}
Add two params from above in your GraphQL schema (I set default page size to be 20)
#extend query
type Query {
findAllTests(page: Int = 0, size: Int = 20): [Test]!
countTests: Int!
}
Since I have no experience with GraphQL, I'm not sure if this works, but you can give me feedback if there are some problems.
I'm trying to make sortable/pageable/filterable repository with multiple filter methods. This is how my relevant code looks right now:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name", length = 50, nullable = false)
private String name;
The repository:
#Repository
public interface UserRepository extends JpaRepository<User, Long> ,
QuerydslPredicateExecutor<User> {
}
And the controller:
#RequestMapping(path="/test")
public #ResponseBody ResponseEntity<Object> foo
( #QuerydslPredicate(root = User.class) Predicate predicate, Pageable pageable) {
return userRepository.findAll(predicate,pageable);
}
It is working perfectly fine, like this:
/users/?page=0&limit=1&sort=name,ASC&name=testuser
But i can't use any other filter method except equals like "name=testuser"
I was searching around and i keep finding guides like this but i'd have to write a PathBuilder for every entity and the controller looks way uglier too.
Is there a way to work around this and keep everything simplified like now? I need the basic operators like eq,neq,gte,lte,like, etc...
Generally I use the CriteriaBuilder API. And it gives me a small solution, all you need to do is subscribe the repository to your custom spec.
public class CustomerSpecification implements Specification<CustomerDetail> {
private C2Criteria criteria;
public static CustomerSpecification of(C2Criteria criteria) {
return new CustomerSpecification(criteria);
}
private CustomerSpecification(C2Criteria criteria) {
this.criteria = criteria;
}
#Override
public Predicate toPredicate
(Root<CustomerDetail> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
return getPredicate(root, builder, criteria);
}
}
public <T> Predicate getPredicate(Root<T> root, CriteriaBuilder builder, C2Criteria criteria) {
if (criteria.getOperation().equalsIgnoreCase(">")) {
return builder.greaterThanOrEqualTo(
root.get(criteria.getKey()), criteria.getValue().toString());
} else if (criteria.getOperation().equalsIgnoreCase("<")) {
return builder.lessThanOrEqualTo(
root.get(criteria.getKey()), criteria.getValue().toString());
} else if (criteria.getOperation().equalsIgnoreCase(":")) {
if (root.get(criteria.getKey()).getJavaType().equals(String.class)) {
return builder.like(
root.get(criteria.getKey()), "%" + criteria.getValue() + "%");
} else {
return builder.equal(root.get(criteria.getKey()), criteria.getValue());
}
}
And my criteria class is:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class C2Criteria {
private String key;
private String operation = ":";
private Object value;
}
And my JpaRepository looks like:
public interface CustomerDetailRepository extends JpaRepository<CustomerDetail, Long>, JpaSpecificationExecutor<CustomerDetail> {
}
In your controller you can use it by getting the object from the queryString.
#GetMapping(value = "renew")
public ResponseEntity renew(#NonNull PageDto page, #NonNull C2Criteria criteria) {
Page<InsuranceRenew> renews = this.insuranceService.getRenew(page, criteria);
return ResponseEntity.ok(renews);
}
and the insuranceservice method looks like:
#Override
public Page<InsuranceRenew> getRenew(#NonNull PageDto page, #NonNull C2Criteria criteria) {
Pageable pageable = PageRequest.of(page.getPage(), page.getSize(), new Sort(page.getSort(), page.getOrderBy()));
InsuranceRenewSpecification specification = InsuranceRenewSpecification.of(criteria);
return this.renewRepository.findAll(specification, pageable);
}
You can see that I used a PageDto class, which is just a POJO with some fields for pagination purposes and it is defined as:
#Data
public class PageDto {
private int page;
private int size = 10;
private Sort.Direction sort = Sort.Direction.DESC;
private String orderBy = "id";
}
As you can see, I used to use the id as the default order by to prevent no wanted exceptions and de order DESC as default.
Hope it helps.
I have just completed an upgrade from Hibernate 3.6 to 4.1.3 Final and at first everything seemed to go fine. However, one of my colleagues recently tested this an in one scenario he gets a NullPointer being thrown from within Hibernate (and this exception was not being thrown before we upgraded for the exact same DB). It is an incredibly strange scenario. We have an entity called BlogPost that looks like the below and it extends some mapped superclasses (that I have also included):
#Entity
#Table(name = "blog_post")
public class BlogPost extends CommunityModelObject implements HasFeedPost {
#Lob
private String title;
#Lob
private String content;
#Enumerated
#Column(nullable = false)
private CBlogPost.Status status = CBlogPost.Status.UNPUBLISHED;
// Reference to the feed post that indicates that this blog post has been published
#OneToOne
#JoinColumn(name = "feed_post_id")
private FeedPost feedPost;
#ManyToOne
#JoinColumn(name = "posted_by_employee_id")
private Employee postedBy;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public CBlogPost.Status getStatus() {
return status;
}
public void setStatus(CBlogPost.Status status) {
this.status = status;
}
#Override
public FeedPost getFeedPost() {
return feedPost;
}
#Override
public void setFeedPost(FeedPost feedPost) {
this.feedPost = feedPost;
}
public Employee getPostedBy() {
return postedBy;
}
public void setPostedBy(Employee postedBy) {
this.postedBy = postedBy;
}
}
#Filter(name = "tenantFilter", condition = "(tenant_id = :tenantId or tenant_id is null)")
#MappedSuperclass
public abstract class CommunityModelObject extends ModelObject {
#IndexedEmbedded(prefix = "tenant", indexNullAs = IndexedEmbedded.DEFAULT_NULL_TOKEN)
#ManyToOne
#JoinColumn(name = "tenant_id")
protected Tenant tenant;
public Tenant getTenant() {
return tenant;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
/**
* If the Tenant is null then it can be accessed / viewed by the entire "community" / user base
*/
public boolean isCommunityObject() {
return tenant == null;
}
}
#MappedSuperclass
public abstract class ModelObject extends BaseModelObject {
#Id
#GeneratedValue
private Long id;
#Override
public long getId() {
return (id == null ? 0 : id);
}
public void setId(long id) {
this.id = (id == 0 ? null : id);
}
}
#MappedSuperclass
public abstract class BaseModelObject implements java.io.Serializable {
// This annotation ensures that a column is not associated with this member (simply omitting the #Column annotation is not enough since
// that annotation is completely optional)
#Transient
private boolean doNotAutoUpdateDateUpdated = false;
#Version
protected int version;
#Column(name = "date_created")
protected Date dateCreated;
#Column(name = "date_updated")
protected Date dateUpdated;
public abstract long getId();
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public Date getDateCreated() {
return dateCreated;
}
public Date getDateUpdated() {
return dateUpdated;
}
/**
* This will set the dateUpdated to whatever is passed through and it will cause the auto update (pre-update) to NOT occur
*
* #param dateUpdated
*/
public void setDateUpdated(Date dateUpdated) {
doNotAutoUpdateDateUpdated = true;
this.dateUpdated = dateUpdated;
}
public void touch() {
// By setting date updated to null this triggers an update which results in onUpdate being called and the nett
// result is dateUpdated = new Date()
dateUpdated = null;
}
#PrePersist
protected void onCreate() {
dateCreated = new Date();
}
#PreUpdate
protected void onUpdate() {
if (!doNotAutoUpdateDateUpdated) {
dateUpdated = new Date();
}
}
#Override
public boolean equals(Object obj) {
long id = getId();
if (id == 0) {
return this == obj;
}
//Use Hibernate.getClass() because objects might be proxies
return obj != null &&
obj instanceof BaseModelObject &&
Hibernate.getClass(this) == Hibernate.getClass(obj) &&
getId() == ((BaseModelObject)obj).getId();
}
#Override
public int hashCode() {
Long id = getId();
return id == 0 ? super.hashCode() : id.intValue();
}
#Override
public String toString() {
return getClass().getSimpleName() + "-" + getId();
}
}
The strangest thing is happening when I query BlogPost in some scenarios. If I run the query below, for example, in isolation then it works fine but if I run it in amongst a bunch of other queries then I get the exception below:
select b from BlogPost b
java.lang.NullPointerException
at org.hibernate.event.internal.DefaultFlushEntityEventListener.isUpdateNecessary(DefaultFlushEntityEventListener.java:240)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:163)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:225)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99)
at org.hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:55)
at org.hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1153)
at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1208)
at org.hibernate.internal.QueryImpl.list(QueryImpl.java:101)
at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:256)
Now the kicker is that if I take all of the fields from all of the mapped superclasses that I listed above and put them directly into BlogPost and make BlogPost just extend nothing and implement java.io.Serializable then everything works perfectly. This leads me to believe that the bug is either related to mapped superclasses or the Hibernate filter that I am applying to CommunityModelObject.
Any ideas as to how to solve this? I am assuming that it is a newly introduced bug in Hibernate but I may be wrong. This is causing major issues for us since we need to upgrade asap in order to upgrade Hibernate Search which we need to do for a critical bug fix.
Also note that the DB we are using is MySQL with the following custom dialect that I wrote when doing this upgrade to handle our BIT columns:
public class MySQL5InnoDBDialectExt extends MySQL5InnoDBDialect {
private static final String BIT_STRING = "bit";
public MySQL5InnoDBDialectExt() {
super();
registerColumnType(Types.BOOLEAN, BIT_STRING);
}
}
Thanks,
Brent
I sorted this issue out, found the problem by fluke. Here is the resolution as I posted it on the Hibernate forum:
I found the issue. It does not seem to be related to interceptors,
rather to either caching or instrumentation. Basically our app
automatically includes all entities within a very specific package in
our caching scheme and the same classes in our instrumentation. We
generally have all of our entities in this package, however this one
which was causing the issue was the only one not included in this
package. The previous version of EhCache / Hibernate that we were
using seemed ok with this, but after upgrading it caused issues.
Anyway, the entity was in the incorrect package, when I refactored it
and moved it into the correct package then everything worked! So it
was not a bug in Hibernate, just an informative exception that made it
difficult to track this issue down (I basically solved it by complete
fluke).
Hope this helps somebody, but in my case it was a problem with a wrong instrumentation.
I have class 'A' and two child classes 'B' and 'C'. 'A' class has a lazy property and it is instrumented to make the lazy accessing works.
But the mistake was that I didn't instrument the child classes 'B' and 'C', therefore any access to the instrumented property from 'B' and 'C' caused the exception.
When I instrumented 'B' and 'C', the problem went away.