I used to apply org.joda.time.Interval to represent a time interval with fixed start and end times (different from a duration which is independent from specific times) to exchange via REST and store energy schedules in a Spring Boot server application (2.2.2.RELEASE).
I tried different ways to store a org.joda.time.Interval field of an object via JPA/Hibernate:
jadira (7.0.0.CR1) with annotation above the field definition (#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentInterval"))
jadira (7.0.0.CR1) with property spring.jpa.properties.jadira.usertype.autoRegisterUserTypes=true set
However, I always get
#OneToOne or #ManyToOne on de.iwes.enavi.cim.schedule51.Schedule_MarketDocument.matching_Time_Period_timeInterval references an unknown entity: org.joda.time.Interval
Questions:
Is there a way to get hibernate working with org.joda.time.Interval?
What is the preferred solution to migrate from org.joda.time.Interval as java.time does not have a similar interval class?
I ended up writing a custom class:
#Entity
public class FInterval {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#Column
private long startMillis;
#Column
private long endMillis;
public FInterval() {
}
public long getStartMillis() {
return startMillis;
}
public void setStartMillis(long start) {
this.startMillis = start;
}
public long getEndMillis() {
return endMillis;
}
public void setEndMillis(long end) {
this.endMillis = end;
}
public FInterval(Interval entity) {
this.startMillis = entity.getStartMillis();
this.endMillis = entity.getEndMillis();
}
public Interval getInterval() {
return new Interval(this.startMillis,this.endMillis);
}
}
and an attribute converter:
#Converter(autoApply = true)
public class IntervalAttributeConverter implements AttributeConverter<Interval, FInterval> {
#Override
public FInterval convertToDatabaseColumn(Interval attribute) {
return new FInterval(attribute);
}
#Override
public Interval convertToEntityAttribute(FInterval dbData) {
return dbData.getInterval();
}
}
Related
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 using Spring #Scope(value = "session", proxyMode=ScopedProxyMode.TARGET_CLASS) beans for objects that should be shared across a single Http-Session. This will provide for example one "Project" object for each User who is using my application.
To get this working I had to implement an interceptor for Hibernate that is returning the name of the class:
public class EntityProxySupportHibernateInterceptor extends EmptyInterceptor {
private static final long serialVersionUID = 7470168733867103334L;
#Override
public String getEntityName(Object object) {
return AopUtils.getTargetClass(object).getName();
}
}
With this interceptor I can use a Spring CrudRepository to save a Project-entity in the database:
#Repository
public interface ProjectRepository extends CrudRepository<Project, Integer> {
Project findByProjectId(int projectId);
}
Project-entity:
#Component
#Entity
#Table(name = "xxx.projects")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class Project implements Serializable {
private static final long serialVersionUID = -8071542032564334337L;
private int projectId;
private int projectType;
#Id
#Column(name = "project_id")
public int getProjectId() {
return projectId;
}
public void setProjectId(int projectId) {
this.projectId = projectId;
}
#Column(name = "project_type")
public int getProjectType() {
return projectType;
}
public void setProjectType(int projectType) {
this.projectType = projectType;
}
}
Storing the Project in the database works as expected. I can have a look at the database and the correct values are inserted. Now I have a different entity that I'm creating the same way as the project and that I want to save in the database via a CrudRepository.
Here the problem begins. Hibernate is not inserting the values that I have set. Hibernate always only inserts null into the database. Reading the values in my Spring application is working as expected. I think that Hibernate is not using the proxy of the entity but the underlying blueprint of the object. How can I force Hibernate to use the proxy with the correct values?
Repository:
#Repository("DataInput001Repository")
public interface DataInputRepository extends CrudRepository<DataInput, DataInputId> {}
Entity:
#Component("DataInput001")
#Entity
#Table(name = "xx.data_input_001")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
#IdClass(DatanputId.class)
public class DataInput implements Serializable {
private static final long serialVersionUID = -6941087210396795612L;
#Id
#Column(name = "project_id")
private int projectId;
#Column(name = "income")
private String income;
#Column(name = "income_increase")
private String incomeIncrease;
/* Getter + Setter */
}
Service:
#Service("DataInputService001")
public class DataInputServiceImpl implements DataInputService {
#Resource(name = "DataInputMapper001")
DataInputMapperImpl dataInputMapper;
#Resource(name = "DataInput001Repository")
DataInputRepository dataInputRepository;
#Resource(name = "DataInput001")
DataInput datanInput;
#Transactional
public void createDataInput(String json) throws Exception {
dataInputMapper.mapDataInput(json);
dataInputRepository.save(dataInput);
}
public DataInput getDataInput() {
return dataInput;
}
public void setDataInput(DataInput dataInput) {
this.dataInput = dataInput;
}
}
I'm trying to build simple REST for purchases I need 2 methods. The first method should show all purchases sorted by date. The second one removes all purchases for specified date I made a method to add and to get all purchases. Now I'm stuck.
#Entity
#Table (name="purchase")
public class Purchase {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#CreationTimestamp
#Temporal(TemporalType.DATE)
#Column(name="createat")
private Date created;
#Column(name="price")
private BigDecimal price;
#Column(name="currency")
private String currency;
#Repository
public interface PurchaseRepository extends JpaRepository<Purchase, Long>
{
}
#Service
public class PurchaseService {
#Autowired
private PurchaseRepository purchaseRepository;
public void addPurchase(Purchase purchase) {
purchaseRepository.save(purchase);
}
public List<Purchase> getAllPurchase() {
List<Purchase> purchase = new ArrayList<>();
purchaseRepository.findAll().forEach(purchase::add);
return purchase;
}
}
#RestController
public class PurchaseController {
#Autowired
private PurchaseService purchaseService;
#PostMapping("/purchase")
public void addPurchase(#RequestBody Purchase purchase) {
purchaseService.addPurchase(purchase);
}
#RequestMapping("/purchase")
public List<Purchase> getAllTopics() {
return purchaseService.getAllPurchase();
}
}
What I need:
1. method to sort my List sorted by date
2. method that removes all purchases for specified date
You can use Spring Data JPA features in these cases.
Add the following methods to PurchaseRepository:
List<Purchase> findAllByOrderByCreatedAsc();
long deleteByCreated(Date created);
And after all, Spring is going to generate an appropriate query based on a method name.
I got it
long deleteByCreated(Date date);
#Transactional
public long deleteAllByDate(Date date){
return purchaseRepository.deleteByCreated(date);
}
#RequestMapping(method=RequestMethod.DELETE, value="/purchasess/{date}")
public long findAllByCreatedBetween(#DateTimeFormat(pattern="yyyy-MM-dd")
#PathVariable Date date){
return purchaseService.deleteAllByDate(date);
}
I have a base class
#MappedSuperclass
#Data //lombok annotation for getters/setter
public class BaseEntity implements Identifiable<Long> {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Version
private Long version;
}
For any derived class Spring Data REST returns JSON without "id" and "version" attributes.
I found 2 solutions:
Projections.
Add getters/setters with another names:
public Long getRevision() {
return version;
}
public void setRevision(Long revision) {
this.version = revision;
}
public Long getIdentifier() {
return id;
}
public void setIdentifier(Long identifier) {
this.id = identifier;
}
Both solutions look like hacks. Does better approach exist?
Showing the ID of the entity is configuring in the RepositoryRestConfigurerAdapter:
#Bean
public RepositoryRestConfigurerAdapter repositoryRestConfigurerAdapter() {
return new RepositoryRestConfigurerAdapter() {
/**
* Exposing ID for some entities
*/
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(MyEntity.class);
super.configureRepositoryRestConfiguration(config);
}
};
}
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.