So I've read tons and tons of posts, forums, question and answers about this topic but I am still quite unsure about what migration means.
Let me first introduce you to my problem.
I have a Spring Boot backend working with MySQL Database with the help of Spring JPA Entities.
Now my problem is the following:
I already have a Spring JPA Entity defined as follows:
#Entity
#Getter
#Setter
public class Blog {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private String content;
private LocalDateTime createdAt = LocalDateTime.now();
}
After having this entity persisted to the database, I realized that the simple String title wouldn't be enough as it maps to a varchar(255) making my Blog posts couldn't exceed 255 characters.
So I would like to change this in the database without running any custom built SQL scripts to modify the column type in the table.
I have something in mind like I would modify the current JPA Entity as follows:
#Entity
#Getter
#Setter
public class Blog {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
#Lob // <--- Notice the Lob here
private String content;
private LocalDateTime createdAt = LocalDateTime.now();
}
If I am right, the Large Object annotation would mean that in the Database the column type becomes longtext allowing a lot more characters than 255 so my blog posts would fit.
So in short this is how I stumbled upon the topic of Database Migration I am just unsure if that is the thing I need in this situation. Can you please confirm if I am right about this, also can you please suggest possible solutions for this particular problem? I know there is a tool called Liquibase used for migrations which is quite popular. Should I go for it, or is there somthing else I would need?
Thanks in advance for the help.
Related
I am trying to choose the properties of associated entities that will be loaded.
For example:
#Entity
#Getter #Setter
public class Book {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "first")
private String first;
#Column(name = "second")
private String second;
#OneToMany(mappedBy = "book", cascade = CascadeType.ALL)
private List<Page> pages = new ArrayList();
}
#Entity
#Getter #Setter
public class Page {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "number")
private Integer number;
#Column(name = "content")
private String content;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "book_id")
private Book book;
}
I would like to have just a SELECT on the Book's first and the content of all associated Page entities
book
- first
- pages
- content
For example, in PostgreSQL this might look something like
SELECT book.first,
array_agg(page.content || ' ')
FROM book
LEFT JOIN page
ON page.book_id = book.id
GROUP BY book.first
I've done my research on how you could go about doing this, most mentioned solution is to use Spring DATA JPA Projections (ref)
I did the following:
public interface FindAllBookProjection {
String getFirst();
List<PageProjection> getPages();
interface PageProjection {
Integer getNumber();
}
}
The issue with projections is that they cause the famous N+1 select problem as the pages are loaded lazily.
I also couldn't find a way to use projections with #Query in the #Repository for the nested properties.
There are other mentions online to use #EntityGraph. From what I understand #EntityGraph will not work as it also selects all properties, specifying only what Entity associations should be loaded.
There are also suggestions on using other libraries like Blaze Persistence (ref) or Hibernate's ResultTransformer (ref) . I would prefer to use only Spring DATA JPA instead of introducing another library for this functionality and writing ResultTransformers seems like adding a lot of boilerplate code to the mix.
To summarize my question is what is the best way to choose what properties are selected for #Entity associations. The main goal is to avoid pulling unnecessary amount of data from the database. The given example above is for demonstration, the data that I am working on includes over 10 columns and spans across 3-4 entities. Having control over the data means better performance.
I am the creator of Blaze-Persistence and I can tell you that if there were an easy way to do this, I would not have created Entity-Views. You can read some of the articles I wrote about this and you will realize that doing this yourself, like you already figured, will require lots of boilerplate code. Doing this efficiently, will require even more code and in the end, you will probably end up with a solution that is inferior to Blaze-Persistence Entity-Views in one way or another and has probably no documentation.
Spring Data Projections is just limited and as I tried to outline many times before, Blaze-Persistence Entity-Views is like Spring Data Projections on steroids.
If you have "just" one or two simple mappings, you might be able to get this done by introducing special #Immutable #Entity, maybe even with #Subselect in this particular case to model what you need, but believe me, this only works good on a small scale. Apart from that, Blaze-Persistence which works on top of JPA/Hibernate enables the use of a lot of advanced SQL features, which you usually can't use from within plain JPA/Hibernate.
In my opinion, ORM libraries should use whole objects, that means loading all data into the program and then transforming/filtering according to logic.
For specific use-cases, where performance is really hindered, I'd use entities in
the database, such as Views/Procedures.
In your case, i'd create a View:
CREATE VIEW book_content as
SELECT book.first as [first],
array_agg(page.content || ' ') as [content]
FROM book
LEFT JOIN page
ON page.book_id = book.id
GROUP BY book.first
And then create a #Repository and #Entity for it in spring.
I built an application with Quarkus and I'm using Hibernate with Panache for the models. Everything goes well, the application starts, but when I call a webservice to get a list using Panache functionalities (.listAll()), I get an empty list and I see the following message in the console:
HHH000183: no persistent classes found for query class: from com.myproject.model.TeamEntity
My models are defined with #Entity annotations that should allow Hibernate to find by itself the entity mappings. Here is an example with the Team model:
#Entity
#Table(name = "TEAM")
public class TeamEntity extends PanacheEntityBase {
#Id
#GeneratedValue(strategy = SEQUENCE, generator = "TEAM_SEQ_GEN")
#SequenceGenerator(name = "TEAM_SEQ_GEN", sequenceName = "TEAM_SEQ", allocationSize = 10)
#Column(name = "ID_TEAM", nullable = false)
private int id;
#Column(name = "NAME", nullable = false)
private String name;
...
}
I don't have any persistence.xml file in the project, only the application.properties linked with Quarkus. Here are the relevant properties extracted from mine:
quarkus.datasource.db-kind=oracle
quarkus.datasource.jdbc.url=jdbc:oracle:thin:/#MYWALLET
%dev.quarkus.datasource.jdbc.url=jdbc:oracle:thin:MYUSER/MYPASSWORD#localhost:1521/SAA
quarkus.datasource.jdbc.driver=oracle.jdbc.OracleDriver
quarkus.datasource.jdbc.min-size=2
quarkus.datasource.jdbc.max-size=10
quarkus.datasource.jdbc.new-connection-sql=alter session set current_schema=MYSCHEMA
quarkus.hibernate-orm.dialect=org.hibernate.dialect.Oracle12cDialect
Does someone know where the problem could come from ? Hibernate should detect entities with annotations and use them in queries automatically.
It came out that the problem was on Quarkus Datasource configuration in the application.properties file. More particularly from this specific line to define the schema used at first connection (I have to admit that was not good looking):
quarkus.datasource.jdbc.new-connection-sql=alter session set current_schema=MYSCHEMA
Replacing the line above with the following solved the problem:
quarkus.hibernate-orm.database.default-schema=MYSCHEMA
In conclusion, I think Hibernate cannot find / does not take the entities defined if this property is not defined, maybe because it makes some kind of detection beforehand. That's only a supposition, if someone knows more precisely how Hibernate works for that specific case, I would be very interested !
I am writing API on Spring Boot have an issue with partial update of entity. When I want to update for example just name of user, spring sees other fields as null and replaces data with nulls in Database. As i read in documentation #DynamicUpdate must fix this issue but it is not working for me.
Here is my user Entity.
#Entity
#Table(name="users")
#DynamicUpdate
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//other fields...
}
Use merge instead
Entity en = sessionFactory.getCurrentSession().get(id);
en.setName("abc");
sessionFactory.getCurrentSession().merge(en);
Performance issue with Entity(
dynamicUpdate = true
)
In a large table with many columns (legacy design) or contains large data volumes, update some unmodified columns are absolutely unnecessary and great impact on the system performance.
Versions:
Hibernate-Core: 5.2.5.Final
Hibernate-Search: 5.5.5.Final
Having the following mappings:
#Indexed
#Entity
#Table(name = "scanresult")
public class ScanResult
{
#Id
private ScanResultKey id;
#Field
#Column(name = "name")
private String name;
}
#Embeddable
public class ScanResultKey implements Serializable
{
#ManyToOne
#JoinColumn(name = "eA", referencedColumnName = "id")
private EntityA entA;
//others...
}
I have read in previous posts that this was an issue in Search 4.4 (when having composite id and foreign relations), but this should be fixed in 5.5. So apparently it is my fault. But I can't figure out what could I do wrong
Exception:
org.hibernate.search.exception.SearchException: HSEARCH000135: Unable to guess FieldBridge for id in entities.keys.ScanResultKey
Note: I only need one field(name) to be indexed
Could you please point out what I'm doing wrong?
OK, Since this question got interest near to none, according to view count, here is, briefly, the way I managed (hopefully) to resolve the problem (Please, correct me if you know more)
Verify modules' versions compatibility
According to one of the commenters in this SO question, not all (even latest) versions are compatible with each other. For example:
Hibernate Search 5.5 works with Hibernate ORM 5.0.x and 5.1.x (NOT
with 5.2.x), and with Apache Lucene 5.3.x, 5.4.x and 5.5.x (Not 6.0)
Stated by: Sanne
This is not a fix to this particular problem, but might save from other issues
Create a FieldBridge for Composite Key implementing
TwoWayFieldBridge
public class ScanResultBridge implements TwoWayFieldBridge
Add annotation to Entity Class, specifying the implementation of Bridge
#FieldBridge(impl = ScanResultBridge.class)
private ScanResultKey id;
I am using Spring with JPA and have 2 entities:
public class DocumentType {
#Id
#GeneratedValue
private Long id;
private String name;
private String idPrefix;
private Integer maxNumberOfSuffixDigits;
private Long startingSuffixNumber;
private String nextDocId;
...
}
public class Document {
#Id
private String id;
#ManyToOne
#JoinColumn
private DocumentType documentType;
...
}
Many Documents can be mapped to the same DocumentType, and there can be many DocumentTypes. The id for the Document will be generated using the combination of parameters found in it's corresponding DocumentType which is: idPrefix + startingSuffixNumber prefixed with 0s to meet the maxNumberOfSuffixDigits constraint. Currently I am on the fence of whether or not I will store the pre-generated nextDocId, but for the purposes of this question (if it makes the solution easier) let's assume that it is available.
The problem I have is getting the nextDocId, and generating the following Id in a concurrent environment. I've looked into Pessimistic write locks, but I do not think that can solve this issue, as from what I've seen, it only locks a single query. I need a way to somehow lock two queries: select query on the nextDocId, followed immediately by an update to generate a new nextDocId.
Any suggestions on how I can achieve this, or other alternatives to this problem would be greatly appreciated.