Spring Specification with #query - java

I have a table named 'content' which has a field named 'created_at'.
I am trying to use pageable and specifications in this table.
Specifications works perfectly but i have a problem with pageable. If i use the inherited method from the repository to search the pageable don't recognize the field with underscore and tries to split him. Givin this error:
"No property created found for type Content!"
If i create a method in the repository pageable works but specifications don't.
Here is my repository:
#Repository
public interface ContentRepository extends JpaRepository<Content,
String>,JpaSpecificationExecutor<Content> {
#Query(value = "SELECT * FROM content", nativeQuery = true)
public Page<Content> findAll(Specification<Content> specification, Pageable pageable);
}
How can i do both?
Content class:
#Entity
#Table(name = "content")
#Setter
#Getter
public class Content {
#Id
#Column(name = "id")
private String id;
#Column
private String name;
#Column
private String description;
#Column(columnDefinition = "TEXT")
private String content;
#Column(columnDefinition = "TEXT")
private String reference;
#ManyToOne
#JoinColumn(nullable = false)
private User author;
#ManyToOne
#JoinColumn(nullable = true)
private Agenda agenda;
#ManyToOne
#JoinColumn(nullable = false)
private ContentType contenttype;
#Column(columnDefinition = "boolean default true")
private boolean enabled;
#Column(columnDefinition = "boolean default false")
private boolean approved;
#Column
private Date sent_at;
#Column
private Date created_at;
#Column
private Date updated_at;
#Column
private Date deleted_at;
}

Avoid using underscores in the entity property names if you have control over the property naming. This will resolve your repository woes, and will result in a cleaner code-base. Developers dealing with the code after you will thank you.
Note, it's not just my opinion: Spring specifically discourages using underscores.
As we treat underscore as a reserved character we strongly advise to
follow standard Java naming conventions (i.e. not using underscores in
property names but camel case instead).
this JIRA issue shows why the documentation was updated with this reccomendation, and the part describing the double underscore option were removed.
I suspect your root problem is that Spring/Hibernate is not mapping camel case property names to the snake case names you have for your columns in the database. What you really need is for your property name to be interpreted in the SQL that hiberate generates as created_at.
Is that why underscores in your property name are "required"? If so, there are a few solutions:
Option 1: #Column annotation
To get JPA/Hibernate to map to the correct column names you can tell it the names explicitly. Use the annotation #Column(name="...") to tell it what column names to use in SQL. Then the field names are not constrained by the column names.
#Entity
#Table(name = "content")
#Setter
#Getter
public class Content {
#Id
#Column(name="created_at")
private String createdAt;
}
Option 2: Improved Naming Strategy
Or if your application has a large number of entities, rather than adding #Column to every property, change the default naming strategy in your configuration file to the hibernate improved naming strategy.
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
This naming strategy will convert camelCase to SNAKE_CASE. Then your class could look as simple as this:
#Entity
public class Content{
#Id
private String createdAt;
}
Using either of those options, when it creates the SQL it will resolve the column names to:
created_at
Note: If you are using, or can use Spring Boot, the auto-configuration default will use SpringNamingStrategy, which is a slightly modified version of the hibernate improved strategy. You won't have to do anything to get this improved naming strategy.
The finish line:
Using camel case in your property names you can write your repository method name using camel case, and you can stop trying to wrangle the double underscore:
#Repository
#Transactional
public interface ContentRepository extends CrudRepository<Content, String> {
#Query(value = "SELECT * FROM content", nativeQuery = true)
List<Student> findAll(Specification<Content> specification, Pageable pageable);
}

Related

Query auto generated table in Spring Boot

I have 2 entities.
One being Courses and other one Batch
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Data
public class Course {
#Id
private String uuid;
#Column
private String tileImage;
#Column
private String description;
#Column
private Boolean isActive;
#Column
private String name;
#Column
private String durationWeek;
#Column
private String durationHour;
#Column
private int price;
#Column
private String apply;
#Column
private Integer linkClicked;
#Column
#OneToMany(cascade = CascadeType.ALL)
private Set<Batch> batches;
}
And one is Batch
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Data
public class Batch {
#Id
private String uuid;
#Column
private Date startDate;
#Column
private Date endDate;
#Column
private String taughtBy;
}
On running in Spring boot, it generates 3 table
Course
Batch
Courses_batches (coueseUUid and BatchUUid)
Issue is I want to query the Courses_Batches table? How can I do that by Spring JPA?
It really depends on the result you want: you probably don't want the tuple Course_Batches which represents the association between Course and Batch, you probably want all Course that matches Batches or the reverse.
This association does not have any specify attribute, and if were to have attributes, there should be an intermediate entity.
You could use a Spring Data #Query, the findBy variant or a Criteria: here I assumed that you can use Java multiline string for clarity, but you would have to use concatenation and space for older version of Java:
#Query("""
select new com.example.foobar.PairCourseBatch(c, b)
from Course c
left join c.batches b
where c.uuid = :courseUuid
and b.uuid = :batchUuid
""")
List<PairCourseBatch> findAllByCourseIdInJoinTable(
#Param("courseUuid") String courseUuid,
#Param("batchUuid") String batchUuid
);
The PairCourseBatch should be a fully qualified type in the query because otherwise JPA would not be able to find it. It expect a constructor taking the course and batch as parameter.
I don't know if you can use generics (eg: Pair<Course, Batch>) but you could return specific attribute and construct a non entity type:
select new com.example.foobar.PairCourseBatch(c.tileImage, b.startDate, b.endDate)
The advantage of using it is cleared in the return type: you don't have to cast component of an Object[].
Spring Data provides many ways to define a query that we can execute. One of these is the #Query annotation.
You can use also native SQL to define our query. All we have to do is set the value of the nativeQuery attribute to true and define the native SQL query in the value attribute of the annotation:
#Query(value = "SELECT * FROM Courses_Batches cb WHERE cb.course_uuid = ?1",
nativeQuery = true)
Object[] findAllByCourseIdInJoinTable(String courseId);
You set the column names according to your structure.

How to stop Hibernate from eagerly fetching a relationship when it is mapped using a column (referencedColumnName) different than the primary key?

I'm mapping a relationship that does not use the entity's primary key. Using "referencedColumnName" with a column different than the primary key causes hibernate to eagerly fetch the association, by issuing an extra select, even when it's tagged with FetchType.LAZY.
My goal is to make it behave like a regular mapping, meaning it wouldn't issue an extra query every time I need to query the main entity.
I have already tried using #LazyToOne(LazyToOneOption.NO_PROXY), which sorts out the problem, but it does not operate well with Jackson's (JSON parsing library) module "jackson-datatype-hibernate5", which skips hibernate lazy proxies when serializing the results.
Here is a scenario almost like the one I have that causes the problem:
Entities:
#Entity(name = "Book")
#Table(name = "book")
public class Book
implements Serializable {
#Id
#GeneratedValue
private Long id;
private String title;
private String author;
#NaturalId
private String isbn;
//Getters and setters omitted for brevity
}
#Entity(name = "Publication")
#Table(name = "publication")
public class Publication {
#Id
#GeneratedValue
private Long id;
private String publisher;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "isbn",
referencedColumnName = "isbn"
)
private Book book;
#Column(
name = "price_in_cents",
nullable = false
)
private Integer priceCents;
private String currency;
//Getters and setters omitted for brevity
}
Repository (Spring-Data, but you could try directly with the EntityManager):
#Repository
public interface PublicationRepository extends JpaReadRepository <Publication, Long>
{
#Query ("SELECT d FROM Publication d WHERE d.publisher = ?1 ")
Optional <Publication> findByPublisher (String isbn);
}
Thanks
The only way to achieve what you are looking for is by moving the annotatation #Id to the isbn property.
You can leave the #GeneratedValue on the autoincrement property.
Notes:
1 - Make sure that your equals/hc are following the OID(Object ID) on your domain case the "NaturalId" ISBN.
2 - It will be good to ensure if possible on DB level that your natural ID has unique contraint on it.

JPA Repository Lob column

Is it possible to implement JPA-repository with filtering by lob-column?
I have the following code:
#Entity
#Table(name = "SUBJECT_IDENTIFIER")
public class SubjectIdentifier implements Serializable {
#Id
#Column(name = "SUBJECT_IDENTIFIER_ID")
private long subjectIdentifierIid;
#Lob
#Column(name = "SOR_BP_GUID", columnDefinition="BLOB NOT NULL")
private byte[] bpGuid;
//getter/setter
}
public interface SubjectIdentifierRepository extends JpaRepository<SubjectIdentifier, Long> {
#Query("select si from SubjectIdentifier si where si.bpGuid= :bpGuid")
SubjectRepository findByBpGuid(#Param("bpGuid") byte[] bpGuid);
}
//test
SubjectRepository byBpGuid = subjectIdentifierRepository.findByBpGuid("D9E70D24567E4DAE8FD3ED5898579092".getBytes());
but I can not find objects from database.
Do I have to implement this query by other way?
Sure, provided that your database supports it.
I recommend writing your query as shown below, as the requirement can be fully resolved without use of the #Query annotation.
SubjectRepository findOneByBpGuid(#Param("bpGuid") byte[] bs);
I'm a bit curious on the columnDefinition specification: is the db column set to the wrong type without that? I would prefer this statement over the use of columnDefinition if possible. This will leave the configuration database agnostic.
#Column(name = "SOR_BP_GUID", nullable = false)
See Also: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation

JPQL validation error

If I don't write a custom query the #Autowired FirstnameRepository object is null, therefore I tried to write a custom query, but this produces the following error.
java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.List FirstnameRepository.findByNation(Nation)!
Since the query looks correct to me, I think it has something to do with my classstructure or incorrect annotations.
FirstnameRepo
public interface FirstnameRepository extends JpaRepository<Firstname, String>{
#Query("SELECT fn FROM Firstname fn WHERE fn.nation = :nation")
List<Firstname> findByNation(#Param("nation")Nation nation);
}
Firstname Model
#Entity
#Table(name = "Firstnames")
public class Firstname extends Name implements Serializable {
private static final long serialVersionUID = 1L;
#Basic(optional = false)
#Column(name = "gender")
private short gender;
#JoinColumn(name = "nation", referencedColumnName = "id")
#ManyToOne(optional = false)
private Nation nation;
public Firstname() {}
}
Since there is also a Lastname model class I extend a class named Name to save Firstname and Lastname in the same Map. Name has no table in the database and both classes only inherit the ID property.
Name class
#MappedSuperclass
public abstract class Name {
#Id
#Basic(optional = false)
#Column(name = "name")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
If I comment the findByNation() method out, the server starts without a problem. I hope this is all you need, the server configuration is more or less copied from a working project, but if I should include something let me know. thanks
EDIT
The Problem was a incorrect configuration. I changed alot of stuff before testing it again, but seems like the main issue was a wrong version of in the web.xml.
Even though this problem has most likely something to do with the Query itself, in my case was a configuration issue. Here are some things I checked
Version of in web.xml which specifies the Version of the used Servlet specification - SO Question
Make sure your database context (e.g. db-context.xml) gets loaded
Your query is malformed... you can't put this "fn.nation = :nation" where Nation is an Entity, you should use a join between tables to work fine.
I suggest you this link.

Explicit constructor using Lombok?

I'm rewriting some messy code that manages a database, and saw that the original programmer created a class mapped to the database like so:
(I've removed unnecessary code that has no purpose in this question)
#Entity
#Data
#EqualsAndHashCode(callSuper = false, of = { "accessionCode", "header", "date" })
#SuppressWarnings("PMD.UnusedPrivateField")
public class PDBEntry implements Serializable {
#Id
#NaturalId
#NotEmpty
#Length(max = 4)
private String accessionCode;
#NaturalId
#NotEmpty
private Date date;
#NaturalId
// We allow for the header to be 'null'
private String header;
private Boolean isValidDssp;
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated = new Date(System.currentTimeMillis());
protected PDBEntry(){}
public PDBEntry(String accessionCode, String header, Date date){
this.accessionCode = accessionCode;
this.header = header;
this.date = date;
}
}
I am still a beginner at Hibernate and using Lombok, but wouldn't this do the same thing and wouldn't Lombok automatically create the needed constructor for you?
#Entity
#Data
#SuppressWarnings("PMD.UnusedPrivateField")
public class PDBEntry implements Serializable {
#Id
#NaturalId
#NotEmpty
#NonNull
#Length(max = 4)
private String accessionCode;
#NaturalId
#NotEmpty
#NonNull
private Date date;
#NaturalId
// We allow for the header to be 'null'
private String header;
private Boolean isValidDssp;
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated = new Date(System.currentTimeMillis());
}
Also, the original programmer of this code says he allows for the header to be 'null', yet he explicitly created a constructor that needs a value for header. Am I missing something or is this a bit contradictory?
Have a look at #NoArgsConstructor, #RequiredArgsConstructor, #AllArgsConstructor.
The constructor behavior of #Data is like #RequiredArgsConstructor:
#RequiredArgsConstructor generates a
constructor with 1 parameter for each
field that requires special handling.
All final fields get a parameter, as
well as any fields that are marked as
#NonNull that aren't initialized where
they are declared.
Given that none of your fields are either final or #NonNull, this will result in a no-argument constructor. However, this is not the most expressive way to achieve this behavior.
What you'll probably want in this case is a #NoArgsConstructor (optionally combined with a #AllArgsConstructor), to clearly communicate the intended behavior, as is also indicated in the documentation:
Certain java constructs, such as
hibernate and the Service Provider
Interface require a no-args
constructor. This annotation is useful
primarily in combination with either
#Data or one of the other constructor
generating annotations.
That bit is contradictory you're right. I've not used Lombok before but with hibernate if you want to be able to create a bean and persist you need the default constructor as given above as far I was aware. It uses Constructor.newInstance() to instantiate new objects.
Here is some hibernate documentation which goes into more detail.
Hibernate Documentation
If you are using #Data with a #NonNull field and still want a noargs-constructor, you might wanna try to add all 3 annotation together
#NoArgsConstructor
#RequiredArgsConstructor
#AllArgsConstructor
Apparently an old intelliJ bug which I did replicate in Eclipse Kepler and lombok v0.11.4
#NoArgsConstructor,
#RequiredArgsConstructor,
#AllArgsConstructor
Generate constructors that take no arguments, one argument per final / non-null field, or one argument for every field. Read this lombok-project
#Data
#RequiredArgsConstructor /*Duplicate method Someclass() in type Someclass*/
#NoArgsConstructor(access=AccessLevel.PRIVATE, force=true) /*Duplicate method Someclass() in type Someclass*/
#Entity
public class Someclass {
#Id
private String id;
private String name;
private Type type;
public static enum Type { X , Y, Z}
}
Fixed it by making member variables final
#Data
#RequiredArgsConstructor
#NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
#Entity
public class Someclass {
#Id
private final String id;
private final String name;
private final Type type;
public static enum Type { X , Y, Z}
}

Categories

Resources