Spring Boot JPA poor performance - spring-boot

I have a simple entity with two fields
#Entity
#Table(name = "lctn_locations")
public class LocationEntity {
#Id
#Column(name = "id", updatable = false, nullable = false)
private UUID id;
#ElementCollection
#CollectionTable(
name = "lctn_location_devices",
joinColumns = #JoinColumn(name = "location_id", referencedColumnName = "id")
)
#Column(name = "device_id")
private Set<UUID> devices = new LinkedHashSet<>();
The lctn_locations table contains 780 entries. The lctn_location_devices table just 11 entries.
I fetch 20 Locations and convert the Entity classes to my DTOs like this:
public Location map(LocationEntity locationEntity) {
Location location = new Location();
location.setId(locationEntity.getId());
long c2 = System.currentTimeMillis();
Set<UUID> devices = locationEntity.getDevices();
for(UUID deviceId : devices) {
location.getDevices().add(deviceId);
}
logger.info("Devices in {}ms", System.currentTimeMillis()-c2);
return location;
}
My issue is that mapping of devices takes ~500ms per location.
Setting the devices field on the Entity to FetchType.EAGER doesnt help much, as then the initial loading from the DB is slower.
The lctn_location_devices has two indexes:
create index location_devices_location_id_device_id
on lctn_location_devices (location_id, device_id);
create index lctn_location_devices_location_id_index
on lctn_location_devices (location_id);
I am not experienced enough with JPA, am I doing anything wrong?

A place to starte might be to log the sql the app sends to the database by adding the following to your application.properties:
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
This will not give you the parameters in the sql, but you might be able to replace the ? in the sql with the relevant values.
Then you can try to run the sql from database tool to see if it is slow then too.
If so, you can try to run explain plan in your database. How do to that can depend on what database you are on. Explain plan will give you some info about which part of your sql which have the highest cost to execute and might give you some info on where to optimize your code.
If you want to look into explain plan you might give a search for explain plan on youtube a shot.

Related

Spring Data + View with Union return duplicate rows

i'm using Spring Boot 2.4.2 and Data module for JPA implementation.
Now, i'm using an Oracle View, mapped by this JPA Entity:
#Entity
#Immutable
#Table(name = "ORDER_EXPORT_V")
#ToString
#Data
#NoArgsConstructor
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class OrderExportView implements Serializable {
private static final long serialVersionUID = -4417678438840201704L;
#Id
#Column(name = "ID", nullable = false)
#EqualsAndHashCode.Include
private Long id;
....
The view uses an UNION which allows me to obtain two different attributes of the same parent entity, so for one same parent entity (A) with this UNION I get the attribute B in row 1 and attribute C in row 2: this means that the rows will be different from each other.
If I run the query with an Oracle client, I get the result set I expect: same parent entity with 2 different rows containing the different attributes.
Now the issue: when I run the query with Spring Data (JPA), I get the wrong result set: two lines but duplicate.
In debug, I check the query that perform Spring Data and it's correct; if I run the same query, the result set is correct, but from Java/Spring Data not. Why??
Thanks for your support!
I got it! I was wrong in the ID field.
The two rows have the same parent id, which is not good for JPA, which instead expects a unique value for each line.
So, now I introduced a UUID field into the view:
sys_guid() AS uuid
and in JPA Entity:
#Id
#Column(name = "UUID", nullable = false)
#EqualsAndHashCode.Include
private UUID uuid;
#Column(name = "ID")
private Long id;
and now everything works fine, as the new field has a unique value for each row.

How to create join table with extra column with JPA annotations?

I need for a project to join 2 SQL tables implemented like this :
I know that I'm not supposed to implement the table IngredientList as an object cause it's only here for SQL structure.
My code goes like this :
#Entity
#Table(name="recipe")
public class Recipe {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id_recipe")
private Long id;
#OneToMany
#JoinTable(name="liste_ingredients", joinColumns = #JoinColumn(name = "id_recette",
referencedColumnName = "id_recette"),
inverseJoinColumns = #JoinColumn(name = "id_ingredient",
referencedColumnName = "id_ingredient"))
List<Ingredient> ingredients;
/* Getter/Setter/Constructor */
}
Which is the classic way but with that I lose the Quantity attribute that I want to associate with ingredient. And I don't get how I can work around this without creating an object IngredientList.
Thanks in advance.
Nevermind that I got my answer gonna edit it soon with code, for anyone with the same question.

Sort entities by count of *-to-Many association in Spring Data JPA

I'm developing a REST API (blog) using Spring Boot running with MySQL database.
Simply, I need to sort Article by the number of likes it has. The information about number of likes is stored in article_likes table which acts like association between Article and User (who liked the article).
#Entity
class Article {
#ManyToMany
#JoinTable(
name = "article_likes",
joinColumns = #JoinColumn(name = "article_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id")
)
Set<User> likedBy = new HashSet<>();
#Column(name = "created")
#CreationTimeStamp
LocalDateTime added;
}
I'm having a service which returns a Page of Article for given Pageable (comes from frontend):
Page<Article> getArticles(Pageable pageable);
In frontend, when I'm showing list/page of articles, I'm using following call to get the last (newest) 5 articles.
localhost:8080/api/articles?size=5&sort=added,desc
I want to do the same with but with number of likes, something like this:
localhost:8080/api/articles?size=5&sort=likedBy,desc
I've tried following variations without the success.
sort=likedBy.size
sort=likedBy.count
sort=likedBy.length
with following error:
No property * found for type User! Traversed path: Article.likedBy.",
Is something like this possible? If not, is there a way to store just the read-only count of likes in Article (as attribute) instead? Because in this particular situation its not important who liked it, but only the count.
class Article {
#Transient
#ManyToMany
???
long likes;
}

How to Seed Data to H2 DB in Spring Boot App with JPA

A little bit of background:
I work on a Spring boot app in a large corporation and we have no control over the Database Schema, table names, and column names. Because of this, our table and column names have no obvious meaning to them.
We are currently seeding data with the schema.sql and data.sql files in the resources directory. This hasn't been working for our team, because of the effort to seed the data with these obscure table and column makes. We often end up looking through our QA server for an account, then writing our code against a QA database.
My question:
How do I keep the schema.sql and data.sql files, but enable our team to seed data to the H2 database by using a seeder made available by JPA or Spring Data JPA.
I found a few examples of using JPA to seed data, but they don't mention where the files should be stored or how the files get called by the Spring Boot app on start up.
Our application only pulls data, and never inserts, so will I have to override the save function from the JpaRepository in order to accomplish this? Or can I just create an entity, and call the JpaRepository's save function?
Here is an obfuscated example of our account entity and repository:
AccountEntity.java
#Data
#Entity
#Table(name = "Table_Name")
#SecondaryTables({
#SecondaryTable(name = "Table_Name2", pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "ACCT_ID", referencedColumnName = "ACCT_ID") }),
#SecondaryTable(name = "Table_Name3", pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "ACCT_ID", referencedColumnName = "ACCT_ID") }),
#SecondaryTable(name = "Table_Name4", pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "ACCT_ID", referencedColumnName = "ACCT_ID") })
})
public class AccountEntity {
#Column(name = "ACCT_ID")
#Id
private Integer accountIdNumber;
#Column(name = "SOME_OTHER_COLUMN1")
private String someOtherColum1;
#Column(name = "SOME_OTHER_COLUMN2", table = "Table_Name3")
private String someOtherColum2;
#Column(name = "SOME_OTHER_COLUMN3", table = "Table_Name4")
private Integer someOtherColum3;
...
}
AccountRepository.java
#Repository
public interface AccountRepository extends JpaRepository<AccountEntity, Integer> {
public AccountEntity findByAccountIdNumber(Integer accountNumber);
public List<AccountEntity> findAllByProp1Prop2AndProp3(
String prop1, String prop2, String prop3);
}

JPA Bi-directional OneToMany does not give me the desired collection

Im trying to map a oracle db model with JPA entities (Using eclipselink) - i have following simple setup :
table program with primary key id
table multi_kupon with compound primary keys id, spil and foreign key program_id
when i try to fetch program with a simple select i would expect the go get a list
of multi_kupon's but im getting a list of size 0. I've made certain that when i do
a select with joins i get data from both program and multi_kupon.
I believe that its got to do with my relations of the 2 entities - hope somebody can point me to my mistake(s)
Snippet from Entity 'Program' :
#Entity
#Table(name = "PROGRAM", schema = "", catalog = "")
public class Program implements Serializable{
#Id
private Integer id;
private List<MultiKupon> MultiKuponList;
#OneToMany(mappedBy = "program", fetch = FetchType.EAGER)
#JoinColumns({#JoinColumn(name = "id", referencedColumnName = "id"),
#JoinColumn(name = "spil", referencedColumnName = "spil")})
public List<MultiKupon> getMultiKuponList() {
return multiKuponList;
}
Snippet from Entity 'MultiKupon' :
#Entity
#Table(name = "MULTI_KUPON", schema = "", catalog = "")
public class MultiKupon implements Serializable {
private Integer id;
private String spil;
private Program program;
#ManyToOne
public Program getProgram() {
return program;
}
My stateless bean :
#Stateless
public class PostSessionBean {
public Program getProgramById(int programId) {
String programById = "select p from Program p where p.id = :id";
Program program = null;
try {
Query query = em.createQuery(programById);
query.setParameter("id", programId);
program = (Program) query.getSingleResult();
I do get the correcct program entity with data, but the list with
multi_kupon data is size 0
What im i doing wrong here ??
The mapping is incorrect, as you have specified both that the OneToMany is mappedBy = "program" and that it should use join columns. Only one or the other should be used, and since there is a 'program' mapping, I suggest you use:
#OneToMany(mappedBy = "program", fetch = FetchType.EAGER)
public List getMultiKuponList()
And then define the join columns on MultiKupon's ManyToOne mapping to define which fields should be used for the foreign keys to the Program table.
Since you have made this a bidirectional relationship, you must also maintain both sides of your relationship. This means that every time you add a MultiKupon and want it associated to a Program you must both have it reference the Program, AND add the instance to the Program's collection. If you do not, you will run into this situation, where the cached Program is out of sync with what is in the database.
It is much cheaper generally to keep both sides in sync, but if this is not an option, you can correct the issue (or just verify that this is the situation) by calling em.refresh(program). If the mappings are setup correctly, the instance and its list will be repopulated with what is in the database.
According to http://sysout.be/2011/03/09/why-you-should-never-use-getsingleresult-in-jpa/
Try to retrieve first program object of List:
List<Program> pList = query.getResultList();
if(!pList.isEmpty()){
return pList.get(0);
}

Resources