Query auto generated table in Spring Boot - java

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.

Related

Using Enum values in #Query JPQL and #CreationTimestamp hibernate

I am stuck with some problem about using JPQL with spring data
Here is my code snippets
#Entity
#Table
//...
public class Parent {
#Id
private String id;
#Enumerated(EnumType.STRING)
private Status status;
#CreationTimestamp
private OffsetDateTime createTs;
#UpdateTimestamp
private OffsetDateTime updateTs;
#OneToOne(mappedBy = "parent", cascade = CascadeType.MERGE, optional = false)
#PrimaryKeyJoinColumn
private Child child;
//... getters setters and constructors
}
#Entity
#Table
//...
public class Child {
#Id
// is primary key in child table and refers to parent.id
private String parentId;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#MapsId
#JoinColumn(name = "parent_id", referencedColumnName = "id")
private Parent parent;
//... getters setters and constructors
}
#Repository
public class ParentRepository extends CRUDRepository<Parent, String> {
#Query(value =
"from Parent p " +
"left join Child c " +
"where p.status in ('status1','status2') " +
"and p.updateTs between :fromUpdateTS and :toUpdateTS")
List<Parent> customQuery(OffsetDateTime fromUpdateTS, OffsetDateTime toUpdateTS);
}
So my first problem is that in native sql this custom query works just fine but once i needed to switch to JPQL, i figured out that it seems like there is no way to use IN clause with Enum collections, that is not passed as a named parameter in the query and this query doesn't work. And same thing is about 'between' keyword for timestamps, i tried < and > instead of 'between', but didn't succeed. So question is - what is proper way to construct such a query using JPQL in #Query annotation for CrudRepository.
I would rather avoid adding additional named parameter like ':statuses' cause it doesn't make any sense at all.
Second question is when i use parentRepository.save(parent) everything works good and timestamps is being created correctly by hibernate. But! When i just remove 'optional = false' from One-to-one mapping in Parent entity class, it starts giving me an error from database about null value of not null field create_ts (createTs in java class). And i am extremely confused with the fact that mandatoriness of the related entity as a hint for hibernate affects timestamp generation of completely other field. I know that 'optional' tells hibernate either to load related entity or not for setting correct proxy and lazy loading doesn't work with optional one-to-one mappings.
Could someone explain this behaviour to me please? I would really appreciate it

JPQL query to return a custom DTO, coming from a parent entity and it's nested/child entity, WITH NULLABLE NESTED ENTITY

I'm currently struglling with a JPQL custom query supposed to be simple, at least at first glance.
Stack: Java 11, spring-boot 2.4.5, spring-boot-starter-data-jpa 2.4.8, hibernate-core 5.4.16, Postgre database.
CASE
I just need my JPQL query to retrieve 3 fields, coming from a parent entity and it's child/nested entity (mapped as a one to one unidirectional relationship), in a custom DTO, instead of an entity from the domain.
The domain is like this:
#Entity
#Table(name = "Item")
public class ItemEntity {
#Id
private Long id;
#NotNull
private String field1;
#ManyToOne
#JoinColumn(name = "nestedEntity_id", referencedColumnName = "id")
private NestedEntity nestedEntity;
//...
}
#Entity
#Table(name = "Nested")
public class NestedEntity {
#Id
private Long id;
#NotNull
private String field1;
#NotNull
private String field2;
//...
}
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class MyDTO {
#NotNull
private String myField;
private String concatedNestedFields;
private String otherNestedField;
//...
}
I thought it was easy, and did something like this:
#Query("SELECT new my.package.MyDto(itemEntity.field1, CONCAT(itemEntity.nestedEntity.field1, ' ', itemEntity.nestedEntity.field2), itemEntity.nestedEntity.field3) FROM ItemEntity itemEntity WHERE itemEntity.country = :country")
MyDTO findByCountry(#Param("country") CountryEnum country);
PARTICULARITY
I don't know if it is relevant or not, but the nested entity fields are #NotNull annotated.
PROBLEM
The problem occurs when the nestedEntity is null: the query return nothing, despite the 'parent' ItemEntity exists.
If the nestedEntity is not null, the query works.
WHAT I TRIED
I tried to use the COALESCE() function, which returns the first non null value from the parameters we give, on every nestedEntity field, as following:
#Query("SELECT new my.package.MyDto(itemEntity.field1, COALESCE(CONCAT(itemEntity.nestedEntity.field1, ' ', itemEntity.nestedEntity.field2),'-'), COALESCE(itemEntity.nestedEntity.field3), '-') FROM ItemEntity itemEntity WHERE itemEntity.country = :country")
MyDTO findByCountry(#Param("country") CountryEnum country);
But it doesn't work either.
UPDATE
I just tried something to eliminate some root causes.
If I run a JPA Named query provided by spring-data / JPA and returning the entity instead of my custom DTO, it works, even with a nested entity null. It does retrieve the entity with it's nested null entity.
The query is like:
ItemEntity findItemEntityByCountry(#Param("country") CountryEnum country);
I'm not sure what to conclude about that, but it may help those who understand JPA better than me (and that's a lot of people... XD).
I didn't find any online resources for that case and I'm a bit lost.
I would be very grateful if you guys could help me with this little surprisingly tricky query!
Thanks a lot for your time guys! Hope this can help others too :)
try the following left join
#Query("SELECT new my.package.MyDto(itemEntity.field1,
CONCAT(nestedEntity.field1, ' ', nestedEntity.field2),
nestedEntity.field3)
FROM ItemEntity itemEntity left join itemEntity.nestedEntity as nestedEntity
WHERE itemEntity.country = :country")
MyDTO findByCountry(#Param("country") CountryEnum country);
otherwise don't use concat and simply pass nestedEntity.field2 and do the concatenation java side

Spring Specification with #query

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);
}

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.

efficiently loading collection of collections from database in hibernate

I have an web application with hibernate which manages data in multiple languages. Currently basically every request generates a shower of select statements on the languagetranslations. The models are roughly as following:
Data <1-1> Placeholder <1-many> languageTranslation <many-1> language
If I query for all/many Dataobjects, I see lots of single selects which select one languageTranslation for the placeholder. The SQL I optimally would want to generate:
SELECT * FROM data join placeholder join languagetranslation
WHERE data.placeholder_id = placeholder.id
AND languagetranslation.placeholder_id = placeholder.id
AND languagetranslation.language_id = ?
so that I get every data with placeholder with translation in one single call. The languagetranslations have an composite primary key of language_id and placeholder_id.
I have no HBM file, everything is managed with annotations. Modelcode (only relevant sections are shown):
#Entity
public class Data {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, optional = false)
#Fetch(FetchMode.JOIN)
private Placeholder content;
}
public class Placeholder {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "primaryKey.placeholder", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#Fetch(FetchMode.JOIN)
private Set<LanguageTranslation> languageTranslations = new HashSet<>();
}
public class LanguageTranslation {
#EmbeddedId
private LanguageTranslationPK primaryKey = new LanguageTranslationPK();
#Type(type = "org.hibernate.type.StringClobType")
private String text;
}
#Embeddable
public class LanguageTranslationPK {
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private TextPlaceholder textPlaceholder;
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private Language language;
}
public class Language {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
I experimented with FetchType and FetchMode but couldn't generate the behavior I want, it always single selects for single languageTranslations.
I also tried multiple ways to query, criteria based, HQL, and raw SQL. My current raw SQL query is the following:
String sql_query = "select data.*, lt.* from Data as data join languagetranslation as lt on data.content_id = lt.textplaceholder_id";
Query q = getSession().createSQLQuery(sql_query).addEntity("data", Data.class).addJoin("data.content_id", "data.title").addJoin("lt", "data.content.languageTranslations").setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
return q.list();
Am I doing something generally wrong here? How can I convince hibernate to get all entities in one single database call? Or is there some other methods to improve performance in my case (e.g. batch selecting)?
You may create proxy pojo which have your all entity variables with getter setter and constructor. then initialize this constructor in hibernate query so that you just get all needed data from database.
import com.proxy;
class userProxy{
private string name;
private string password;
private string address;
private int pincode;
private byte[] profilePic;
private int age;
public userProxy(string name,string password){
this.name = name;
this.password = password;
}
//Getter and setter of all variable...
}
Then use this constructor to Hibernate query like
select new com.proxy.userProxy(user.name,user.password) from usertable
Am I doing something generally wrong here?
No, you are not. That is how Hibernate works.
How can I convince hibernate to get all entities in one single database call
You have to use HQL or SQL query to do that. You do not need to have HBM file. It can be done through #NamedQueries / #NamedQuery annotation with list method.
There are many samples on Internet as example simple one:
http://www.mkyong.com/hibernate/hibernate-named-query-examples/

Categories

Resources