About this talk
This talk focuses on improving application performance by effectively managing database connections in Java applications using Spring Boot and JPA. The speaker discusses the importance of connection pooling, specifically using HikariCP, and demonstrates through benchmarks how it significantly speeds up database connection times compared to using a direct driver manager. Challenges related to managing connections are explored, including the detrimental effects of holding connections open during external calls and the benefits of marking transactions as read-only. The talk further delves into JPA mappings and optimizing data retrieval through lazy loading and DTO projections, emphasizing best practices for minimizing redundant queries and improving performance. By sharing coding examples, the speaker illustrates how to implement these techniques in a real application, highlighting the balance between functionality and performance.
Full transcript
So welcome everyone. Thanks for being here for still being here as we're approaching the very end of J Spring. I really appreciate that and well as a token of my enormous appreciation I decided to annoy you with this loading spinner. Cuz it's it's really annoying, isn't it? I think everyone in the world hates that. And we as software developers don't like to create things that most people
in the world hate. But still we as software developers create slow applications from time to time. And I'm guilty as charged. I recently did it myself with a really important application. Well, at least to me that was an And let me tell you a bit about myself to to explain that. First of all, I think my my parents would describe me as someone putting the QR code
for Slido on his second slide. And well, that's spot on because here it is. I I did that. Other things to tell about myself, I'm a freelancer. Companies hire me from time to time. I've been doing that for over a decade now. Um More recently I wanted to public speaking as you're witnessing now. And on top of that I have probably like most of you regularly the
best idea ever. Now, my most recent best idea ever is is something I want to show to you. It's a fun fact store. And here I want to share a fun fact that I acquired in my own store. The fear of long words is called hippopotomonstrosesquipedaliophobia. And I I think the person that came up with that word for that fear is is really a legend. And for
today it also serves another purpose because we are here at J Spring. We love all the techy stuff that is said here, but probably your friends and family at home, to be honest, they they couldn't care less. So, with this fun fact, at least you can bring something home that your friends and family care about. So, only thing you have to do is remember hippopotomonstrosesquipedaliophobia, I think
looking at this J Fall audience, that that won't be a big deal for you. Now, um this is a shop, so we we can buy things here. So, I hope you agree that well, this is a great application. So, let's look a bit into this fun fact store under the hood. It has a relational database, then a server app processing that data, and then a client app
serving it to to the users. Quite a common setup. Um first, zoom in a bit into the client app. It's well, the browser serving HTML based on Thymeleaf templates, styled a bit with CSS. And that is enough to have said about the client app. So, that leaves us well, a nice 42 minutes to talk about things that are probably more interesting, the server app. It has a
relational database, Postgres in my case, but I printed it in gray because for the talk it's not really relevant. It could be any other relational database as well. The server app here runs on Java with Spring Boot 4 and Spring Data JPA. Also a common and convenient setup, but not without challenges. And we will basically talk about two of those challenges. First, you have to connect to
the database. Things can go wrong there. We will zoom into that. Then, once we establish the connections, we want to have the world of the database talk with the world of Java. We can use the the JPA standard for that. but also this is convenient, doesn't come without challenges. first topic. How do we get a connection? Most of the time our server app, well, it asks a
connection to the connection pool, Hikari by default if if configure that in the Spring Boot. The connection pool uses the driver manager to get a connection. The driver manager uses a driver to establish the connection to your relational database. Postgres in the case of my fun fact store. Now, the first question here is, okay, why why do we use this thing here, this connection pool? The server
app could just ask a connection to the driver manager. Yeah, well, okay, that that could work. That is possible, but it's it's way faster. You probably feel that, but how much faster is that? Well, I actually I was wondering that, so I wrote us a unit test to investigate a bit on that. Here we the fun fact store and the class that I was telling about was
a connection timer test. What you see here is that I import test containers that spins up a Postgres It's a data JPA test, so I have the Hikari data source. It basically has two tests. One uses my connection timer test that overrides here an abstract method to get the connection. It gets the connection here directly from the connection pool. And the connection timer, it connects 1,025 times.
It ignores the first 25 times for warm-up stuff, and then it prints statistics on the remaining thousand attempts. Basically, this test does almost the same, but it uses the driver manager with the properties from the connection pool. So, it basically connects to the same underlying So, yeah, let's run the test and see what that brings us. So here, basically it spins up a Spring Boot with enough
layers to run this And here we see that a thousand the connection pool is it's connected in a little less than a millisecond. When we use the driver manager directly, it's almost 4 seconds. So, yeah, that's that's worth the trouble. It's like five five thousand times faster. Um so okay, that's why we use a connection pool basically. That's interesting. So let's find out a bit more about
this connection pool. You can find out more about the connection pool too by increasing the log level of Hikari that provides the connection pool to debug and then well, basically just restart your application. So if you want to find out, you can do this in your And you see all kind of stuff locked here. You can study that. We we don't have the time in this talk.
But I want to zoom into And that is Where is it here? The maximum pool size, which is 10. 10 is the default as you see I didn't configure anything. Probably in your production system this is a bit of a bigger number. But the key thing here is that it is a finite number. So what happens if 10 connections are in use and the 11th user comes
in? That user gets the annoying loading spinner, which we want to prevent. So what what we want as developers is to get the connection just before we need it. And if we don't need it anymore, give it back to the pool right away so another user can use it. But if you want to optimize for that, how can we do that? So let let me show you
a bit of the code fun fact store. So here we have a a get endpoint for the show shop. It corresponds to this one. You see here my my last purchase date. so basically that enters here. And then it adds the username to my model. It's going to get the last purchase date in the method here, the transactional method. First of course we check if somebody's criminal.
We won't we don't want any criminal in our store. But if he's not a criminal, then we fetch the last purchase date. That one is returned. Then we send an email to the CEO because we are very happy we have a potential customer in our shop and then we return. Okay, this is nice and we can feel that we interact with the database to get the last
purchase date, but I didn't see any get connection or close That is because this is abstracted away. Hibernate takes care of that and that is nice on one hand, but how can we optimize for that as a developer if we don't even know when the connection is acquired and returned to the pool? So first step, let's make that visible. Oh, and by the way, if there's any
code you you like to play around with yourself, I will share a QR code in the end to the GitHub so you can have it all. There are several ways to to make that visible and I think the most simple way is what I did here. I just added a spring bean. That one gets the connection pool and it adds a metric tracker factory. It basically logs
two things, when a connection is acquired and when a connection is returned to the pool together with how long this connection was used. It's logged in debug level so in order to use it today, let's put that level to debug. Do the same for the shop controller and the shop service. So we can follow the flow. Now for our convenience here, I created a shop controller integration
test. It doesn't do that much. It just calls the endpoint, but that's basically enough for us. Because we can follow the flow, see when connection is established, when a connection is returned to the pool. So I run this test, Spring Boot is started. Yeah, and let's see if we can we can So here we are entering the shop controller as we read here. That's this part. Then
we get the last and check if somebody is a criminal. That is here. So just well, around here we acquired a connection apparently. Uh then we well, asked the for the last purchase date. Then we are here. We return to the controller method as you see here. We sent an email to the CEO. Then we are in the end of the controller and then we return the
connection to the pool. And we used it for 646 milliseconds. Okay, now at least we know when we get the connection and we return it to the pool. So let's zoom into when do we return connection? And that's already a bit odd, isn't it? Because I I might expect here after my that the connection was returned to the pool, but this is not true. It's only returned
after this method. And this is not transactional and at the class level also not transactional, but still it's kept open here. Anyone knows why? Yes? Mhm. Lazy load maybe it can load additional data was the answer. More or less true. open session in view. This is enabled by default if you run Spring app and the reason is that you can render you can interact with the database
in your in your views. But well, I don't want that and it's not a very good idea anyway because as you see I'm basically done interacting with the database, but still the connection is kept open and I interact with an external system. That is slow and the external system could have problems. It could be really slow. It could even time out and it doesn't even mean that
this user has to wait. If there are enough users using that endpoint, it means your entire application cannot use the database. So all the endpoints interacting with the database, which are most often quite a lot are unreachable. Basically, your application would be down. Um So, uh first thing to do in uh a Spring Boot application is to put that basically to false. Since Spring Boot 2, it
even warns for that uh on the one level here uh that this is enabled by default. So, we don't want that. Uh so, let's put it in our uh properties. So, please don't uh do that. And we run the test. See uh what happens So, here we go. Uh again, it uh launches the Spring Boot instance, and here we are. We see that it returns the connection
right after um the uh the service method, after the transactional method. You see, last purchase date, and then it returns. So, already um some some other user can use the connection when we interact with our uh external email system. So, this is great. Now, let's zoom in to the other end of the equation. When do we acquire a As we saw, it's acquired just before check if
criminal. So, here. And you might think, okay, yeah, this makes sense. This is a transactional method, so we enter transactional method, so we acquire the connection. But Hibernate is a bit smarter than that. It acquires the connection just before it needs it. And if we look at the code here, I think only here we interact with the And again, this is a pity, because here we call
an external system, and all the time the connection is kept open. And for the same reasons as we just discussed, this this could get our entire system down. So, why is Hibernate doing that? Now, uh suppose we have no clue, and we want to investigate uh what you can do always to put the root debug level uh the root log level to to debug and then just
restart. And an awful lot of things are locked. but it's not a problem because we will find our check if criminal here. on this one, of course. Ah, here we are. Check if criminal. Here we have the connection is acquired and in the middle set auto commit to false. basically, that's exactly the reason that Hibernate gets the connection. It wants to be absolutely sure that it is
in control of when stuff is committed. And it can only be sure if at the start of the transaction it gets the connection and sets auto commit to false. So, now Hibernate can decide on itself when it wants to commit stuff. this is a pity. Luckily, there is there is a solution for that. But what what we can do is we can just put auto commit to
false ourselves and then tell Hibernate, "Hey Hibernate, we already did it. Don't worry about it." Now, first step here is to set auto commit to false. You can do that with this property in uh Spring Boot. And then with this property, you can tell Hibernate, "Okay, we already did that." From Spring Boot 3.2 and up, uh it already does that for you. If it finds out that
you did this, uh it automatically tells Hibernate, "Okay, uh the user already uh did that. So, uh no worry." So, okay. Um Next improvement. So, let's restart the application and see what it brings us. So, here we see that we check if if somebody is a criminal, we call the external system, and still there is no connection acquired. It's only acquired here just before we purchase uh
we find the last purchase date, which makes sense, of course, because we have to interact with with the database Okay, so this is this is very nice. But you have to also always be careful about this because you might think okay this get last purchase date. We don't modify any data, right? So yeah, this is potentially a good idea to to do it like this. Because well,
we don't need to modify the data anyway and we can use probably an optimized reading source. But by doing that again a connection is acquired here and this time it is spring. Spring that asked the connection to set read only to true. So in general this might be tempting to do and could be a good idea, but be careful. in this case we were lucky anyway, right?
Because this is the first statement no database interaction here. So we we could use it. We could really optimize. But sometimes of course you have to interact with the database here. And it's seems that in our case we should do that anyway because here we check with an external system if somebody is a criminal by the username of our system. It doesn't really make sense, right? It
turns out that we misunderstood the API and we have to use the email address because as you know an email address uniquely identifies any citizen in the world. So let's use that. And or else throw. And let's use that email check if the user is a criminal. And we well, let's put a log statement. Ah, thank you AI. That makes sense. Now if we run the you
might feel what is what is going to happen here, right? Because here we have to interact with the database. So, our connection is opened and this is executed and then uh while the connection is open, we interact with the external system, which is a pity and we we lose uh stuff there. So, um yeah, to solve that um first we got rid of transactional here and what
you sometimes see is that you move this to another surface and add transactional there and do the same for Uh move it to another artificial surface and put a transactional there. That that works. Uh so, it is a it is a valid solution, but it's a bit verbose. An alternative that I want to show you is to use transactional template. And what you can do with a
transactional template is you can uh execute uh code. And the code that you uh execute there it runs uh in a transactional context. So, if we do that here and we do the same Yeah, let's see what we got ourselves here. Let's rerun test. And well, again our application starts. So, here we see uh we got the email for user Yoss. The connection is acquired. Then it
is returned to the pool used for 11 ms. Then we interact with the external system, but when we are do doing that, the connection is returned to the pool. So, other users can use that connection uh and even if the external system is down, it won't pull our entire uh application down. Uh then if the user turns out not to be a uh well, we just acquire
the connection again and we use that for 4 ms. So, hereby doing some some simple configuration tricks and being aware of what happens, we went from let's say an implementation that interacted once with the database and it took almost 700 milliseconds to uh interacting uh two times and in total it's just 15 yeah, basically that's what I want to tell about the connection. So, let's wrap up
a bit. Why do we use connection pooling? Because it's it's fast and not a bit faster, it's really really fast. So, that's a good reason to do it, but the amount of connections is finite. So, please be careful, use it as short as possible. Um and you have to check some configuration things for that. So, open in view, put that to false. And well, already do the
auto commit to false yourself. Tell Hibernate you did. External calls during the lease of a connection is a bad idea. You don't They take often long by themselves, way longer than queries often and you also don't want to uh have an external application when it has problems pull your application down with it. Transactional templates could be a useful alternative to creating an artificial service and ping-ponging A
a route to get that solved. before we hop on to the next topic, I want to share just a few fun facts with you. Because as I said, I'm from the Netherlands. We well, we we tend to be a bit well, I don't know, greedy they say, but um I'm not because I'm a generous guy. I will just buy you a fun fact. Here we have it.
Octopuses, they have three hearts. It's That's actually true. They have They have two hearts for their gills and one bigger heart for the remainder of their body. That's That's That's a nice one, right? And well, to get a stereotype really out of the way, I I will just buy you all another one. Rats have the ability to laugh. It It's true. You You can You can tickle
a rat and it will it will laugh. Okay. So, so here you are. It also You can use this for your home front. You don't have to remember You can just stick with the other two and you have something interesting you can tell at home. So if they ask you how was Jacon, you can tell some nerdy stuff, but you can also tell the two fun facts.
Maybe they appreciate it more and depending on your loved ones. Um so now we tackle that. Let's continue and um go back to the more techie stuff because now we established a connection to the to the database and um we want to interact with that. Now we are going to use JPA which stands for Jakarta Persistence. Used to stand for Java Persistence API and it is a
standard that is describing exactly that. So how does the world of uh tables, column, and rows relates to the fields to to the world of classes, fields, and Java objects. Um now Hibernate is sometimes confused or intermixed with JPA, but JPA is a standard, doesn't do anything on itself. Hibernate implements that that standard. You well EclipseLink for example it exists as well is another implementer. But Hibernate
is is really well the the most famous and the and the dominant one. If you use Spring Data and you have no clue what implementation you use, it's Hibernate. It's the default. My Fun Fact Store uses Hibernate as well. So um how does that look in the code? Let let let's go back I will show you some. So um the first if a Java class is mapped
to uh a database table, we we call that an entity. You can recognize here it's annotated uh with the Jakarta Persistence Entity annotation. An entity needs an ID. Well, but since all my entities need an ID, I abstracted it away in the mapped superclass. Now you might think, okay, uh this class is called Fun fact, but how does it know to what table it should map? You
can specify that, but I didn't do it because I just followed the naming convention, and that is that this Pascal case uh table name corresponds to the snake uh uh database table name. Uh that's a Spring Data convention. And basically the same holds for fields. Here I have fact. I didn't specify any name but because it's just the same as the column name, as you see here.
Explanation uh is the same and don't get tricked uh that this might not be a map fields because it is. All the all the fields in an entity, well, not the static ones, but uh all the other fields are map fields, also if you don't specify column. It's just that explanation here is nullable, which is the default, and it follows the naming convention, so there's there's nothing
to tell. That's the reason I didn't specify it with column here. Here we have a relation to another entity, uh a many-to-one relation. Uh well, there I specified here a join column, but only to tell it that it's it's not nullable. Again, no name uh because I followed the convention, that is the field name, then an underscore, and then the name of the primary key that you
refer to. Now, my entity here has one method. It's a sanitize method because I want to protect my sensitive users uh to to borrow to to really bad words like like boring and stupid. I I don't want to expose them to those those words, so I sanitize my facts and my explanations in in this method, basically. Uh and if if there are any of those words, I
replace them. Now, this method uh well, I just call that regularly here in my service. So, basically what I do, I fetch all the uh and then I I sanitize them. Here um we have some uh so so method just calling that some test method. Um this probably triggers some SQL, and I I want to dive into do with with you. So, you can make that that
visible um by um So, we can get this one away by putting the lock level of Hibernate SQL to to Now, if we um run the test here, then well, we see what is this going to bring us. I I expect some select all here because fetch all the all the fun facts. Um and let's see what it brings us. So, here we see they fetch all
the facts. uh okay, yes, here is the select all from fun fact that I expected. But then again, there are three other selects, three selects from app user. And then it seems that I fetched 10 fun facts. Now, the select from app user, it probably has to do something with with with this. But why is it is it fetched? I I don't use that at all. Why
is it Why is it fetched? Anyone knows why why is this Yeah, yeah, well, lazy loading basically it's the opposite. It's not la It's It's eager eager It's eager fetched. I'm sorry? Exactly. You You can take over the talk because what I what I want to what I want to show that is uh by default it's fetched eager. And you can just see that here in the
code. You can You can specify the fetch strategy. And if you don't specify anything, it is fetched eager, meaning fetch it right away. So, you can fetch it lazy, meaning only fetch it at the moment you need it. Uh but by default, this is eager. And that is that is the reason that uh we fetched here. So, if you add uh a new many-to-one relation or a
one-to-one relation from the owning side, then it's uh a very good idea to fetch that lazy. the second question, uh I have three select from app user here, but I have 10 10 facts. So, I might expect that 10 select from app user here. Why are there only three? Anyone? Louder. It's but the yet I'm sorry. Three almost three three unique admins because the answer is is
caching. Uh basically what what what is going to is what Hibernate does is that it tries to fetch here the admin that belongs to the fact and it has the app user with the ID. So, if it doesn't know that, it will do a select. But if next fact has the same admin, so the same app user with the same ID, it gets that from the cache.
And that cache is called the persistent context or the first level Um that's nice and in recent versions of IntelliJ, they added a quite a nice feature to make that visible. So, here you see the level one entity cache. So, that is the persistent context and you can actually see what what is in there. So, you see here my 10 fun facts and my three app users.
And it also demonstrates this this caching thing. Because the first fact here, it has an app user with ID 3. Now, the say the second fact also has But if you look at the the object, so in my JVM, what is this object? It is object 22529. It's the same here. That's because of the caching of Hibernate. It just uses the same object. Now, this has two
advantages. And the the first one is obvious, right? We have only three selects instead of 10. So, we win there. But the second one is a bit more subtle that we get a repeatable read guarantee. So, if we use here in uh our app user with ID 3 and we use it here, we have the same object. So, basically our database here has the read committed isolation
level, but on the application level, we have a repeatable read guarantee by by this feature. So, yeah, recall that it's it started all out with this uh problem that we fixed So, let's rerun the test and see if indeed we have fixed something by putting lazy here. So, it's just the same test. It starts up Spring uh and check the query. Yes. Yes, we improved that. So,
we fetched all the facts and now only it does a select all of or on fun fact. And if we do the same trick here, we look into the uh level one entity cache or the first level cache, we see there are only 10 entities here. And here this admin, it's no longer a real app user object. It's a Hibernate proxy thing. And if we open that,
we see there's there's nothing in it. It's only a placeholder. It knows it's an app user, it knows the ID, but the ID it can know that from the foreign key. So, there's there's nothing fetched from the app user um related table. So, cool. We fixed that. But um yeah, the world You know the world, right? It it changes. It changes all the time. So, also in
the case of my fun fact store, no longer we want to sanitize by static list of bad words. want to sanitize by a list of bad words, where the bad words depend on something that we we get from the uh admin here. So, we get it at the top level domain, and depending on what that top level domain is, um yeah, we have other bad words. Okay,
so no worries, we just implement the feature and uh we're good to go. So, let's run it Again, it starts uh the test. Now, uh basically, we're back to where we were with with with a small difference that here we do the Then here we fetch the facts. And then here we have three times the select from app user. Now, why do we have it? At the
very moment, we ask a method here on that string thing that we saw that Hibernate proxy. It fetches it uh right away. Now, in this situation, it is not that bad that we fetched those app users, right? Because we need them. What is not that good is that we fetch them one by one. And this is this is an N + 1 problem. So, we have one
query and then we have N additional queries where N is proportional to the size of the result set of that first query. Um so, how to solve that? Basically, uh we have to tell Hibernate that we want to fetch them right away. So, you can do that with some customization. and here you see I have just have a Spring Data repository where you can annotate with a
query and I say select all from fun fact join fetch the admin. Okay? So, uh with this change in place, let's see again what we got here. Ah. So, now again, we are back to one query with all the joins that we need in uh in place. And when we sanitize it, no longer we need any uh additional uh selects. So, that's cool. the last thing that
I want to to tell here is that we recall that I I don't have any save or save all call to uh to to a minute here. And still, my fun facts are updated. And not because I have bad words. Don't think that of me. Um but because for demo purposes I added a dot. Just to show you that Hibernate will do the dirty checking itself. You
don't need to do the save or save all. Um and it's actually in my opinion just confusing adding that. It's not the most performant way anyway, but it's not needed. Hibernate will take care and find out. um that having said we are modifying data here. But most of time we are not modifying data. We are just reading data. And my application of course reads data as well.
Uh here we have for example a find my facts method. So this class is annotated with transaction read only. This method find my facts it corresponds to this page. So you see here I have my purchase date, my support email, and the text of the fact itself. And that is exactly what is contained in this fact DTO. So we have the purchase date, a text, and a
support email. So what do we do? find the purchases for a user. And then from that purchase we construct a DTO by getting the purchase date. And then we get the fact from that fun fact. And then from that fact we get the admin and get the email. And there we are and we return that. Um now let's see what this brings us in the code. So
let's put fact service log level to to debug. And again here uh I have a unit test. So let's run it. And see what queries this this brings So uh we are finding defects. So, basically this is entering this method. Then we have a select from purchase. Uh it joins uh the app user, but it only does so because it needs to filter on the username. After
that, we found three facts. And from that fact, uh from that purchase, sorry. From the purchase, we uh find the fact because well, we need it here, right? We need to do something with that fact. And we fetch the admin here. So, there's another select. Then we do it for the the second purchase. And from what we've what we've just seen, um this one has apparently the
same admin no select here. Uh and then the third one, it has another admin because it's fetched here. So, um yeah, again, an N + 1 problem, right? One query and then N additional result size of the first uh one. So, we could solve that by uh join fetching, as we've just seen. That that that is an improvement. Um but if you don't modify data, uh we
don't need to fetch uh entity entities anyway. We could uh use a DTO projection, which is better performing uh possibility. Now, what is this DTO projection? It is a a query. What you see here is uh JPQL. Um so, I have here a constructor expression for exactly that same DTO as you just saw from from the code. Uh and then we fetch it from purchase. Well, we
join here um what what what we need. well, this is JPQL, and it has some advantages. We can express the things here in the terms of our entities, and IntelliJ knows that. So, if I do, for example, this, IntelliJ tells me, "Hey, this is not a valid field." Uh sometimes you refactor stuff, so you don't see this in IntelliJ, but if you then try to start Spring
Boot, it it doesn't it doesn't boot. So, it's Spring don't boot anymore or something. But, it's really convenient because instead of hitting this in production, just locally uh the first time you try to start or an integration test you try to run, it says, "Hey, this query is wrong." Which is very nice. Uh also, I don't need to specify in the joins here because, well, it already
knows how it's joined because of this. Um so, let's uh use this and we can of course do this by replacing this code and then rerun the test. So, the first thing to to notice, uh in my opinion, is is readability. So, it's performance for the developer. This is really simple, right? I just call one method. And that method here is very readable. Also helped by the
syntax highlighting of my IDE, but it's very understandable. The second thing uh is the the the performance for the machine. Because if we look at the test here, and we see the query. There there's only one one query. And there are only the fields selected that we actually need. And other than that, it just joins what it needs. So, that's better for performance of the developer, I
think, but and also better for the performance of the machine. Now, this is JPQL. Uh and sometimes it just uh hits a bit short. Uh for example, if I want to use a lateral join or some SQL specific things, uh I want to use a native query. Well, then still you can use this uh DTO projection. And I can show you uh how. You see here, this
is a native query. It means that this I can just copy-paste this to my SQL client. It It will work. And that is of course the reason that there is no uh expression here or something. Um and I have to specify my own joints because I can no longer use that logic, but the upside here is that I can use lateral join or whatever I want that
my relational database understands. I can just use it here. Um, if you use Hibernate 6 or higher, uh, you can do almost the same thing with this record projection. You see I just have an ordered list here of my, uh, my parameters and I can use the constructor of this fact DTO native. Uh, I had to create a fact DTO native because of the type mapping. You
know, SQL timestamp where we had a local daytime. Now, uh, this is not in the JPA specification. It is a feature of Hibernate that is added in Hibernate 6, but if you want to stick with the specification or you don't use, uh, Hibernate 6 yet, then still you are not lost because the officially supported way is the interface uh, based projection. Again, this query is almost the
same thing. Only two additions. I added two name aliases and because they have to correspond to the returned interface according to the Java bean convention. So, this is get uh, and that's why it should be named purchase date. But other than that, I can still do this projection and just return interface for that. recall here that I started with manually, uh, mapping my, uh, entities to those
DTOs. Uh, there are of course libraries doing that. Maybe MapStruct is the most famous. So, who uses MapStruct Yes, and I think you use that to to to map back and forth between, uh, entities and DTOs, stuff like that. And that is, uh, if that is the use case, that is I've seen a lot of projects with that as a use case for for MapStruct. And at
first that is quite convenient, right? Because you just you just add it and it works. But then there is a day that uh, well, you need some customization and you can do that. You can interact. You can recreate well, you can override behavior in the in that mapper But then yeah, of course, that's an additional layer where there can be bugs. So it that is really worth
I think for the performance of the developer because if stuff goes wrong, you also have to investigate that. The performance of the machine, if you look at the queries, then it's probably way worse. A lot of things are fetched and you don't notice that on your own development machine, but if it runs in production really a lot of data is fetched just to to to create small
data that is returned to the well, to the user of the data. So the client app potentially. So in general, I would recommend well, just use a DTO projection. It's often it's just well, the better performed way both for the developer and the machine than than just mapping back and forth between entities whether you use a library for that or not. basically that concludes what I wanted
to tell about JPA. So first thing to tell at fetch type lazy if you introduce a new many-to-one or one-to-one relation. Don't blindly add it to all existing relations because stuff will break and that's basically the reason that this is not changed I think in in in other versions because stuff will break. I think it's a kind of a historical mistake that this was eager fetched anyway,
but it's you cannot really change it now. So you have your own responsibility there. So fetch it lazy. If you need it, join fetch it. But basically only if you need to modify data. If you don't need that, a DTO projection is often way superior. next thing I want to talk about the JPA mappings. It's this is an opinionated slide. It's my order of necessity because with
with the mappings, there are basically two things. Uh one as we've seen, a lot of performance problems come from the mappings or misusing the mappings, right? The second thing is that you have a high amount of freedom in how you use that. So, I think that the mapping of tables to classes as as we do that with with JPA, with the database types to the Java types,
it's it's really convenient. I really love that. I use it all the Then the use of many-to-one and one-to-one, it can be highly useful as you saw in my code example. I I asked the the top-level domain of the admin from my fun fact. I could only do that because I mapped that admin as a many-to-one one relation. But if you don't use that, okay, you can
also uh just map the uh the foreign key as a long. That's That's way simpler. One-to-many can be convenient if you want cascading behavior, but what the the most efficient way to do that is to map it bidirectional. And if you, well, do the many-to-one anyway, maybe you can stick with that. It makes your code The many-to-many annotation, it exists as well. Uh to be honest, I
don't I don't use that uh ever. Uh of course, I have many-to-many relations, uh but I I have one in my application, the relation from fun fact to user. It has a many-to-many relation, but I I mapped the join table and I called it purchase. So, it gave me the possibility to give it a descriptive name, even give it additional fields that make sense, the the the
order date, the the purchase date in this case. So, uh basically, as I said, this is opinionated, maybe what I want to say. You don't have to use it because you can, but use it if it adds value for you that is more than than the cost of using Now tools, uh there are tools that can help us. Uh IntelliJ, as you've seen, has syntax highlighting, also
configuration hints if I forget to add an ID to my entity it will tell me. High persistence optimizer I don't have time to demo that but it's a tool from Vlad Mihalcea. It will warn for things like well I fetch this eager instead of lazy. It will tell you it's not free it's $60 a year I think you have to decide if it's worth it but it's
definitely worth checking it out or all the other content of Vlad Mihalcea. Gatling is a load testing test you can record your traffic and then basically multiplied by 2000 so you can machine gun your own application which is really Now we're almost at the end and well the the the fun fact store that was with us all the time you you might think okay yeah this is
this is a So I want to share my dreams with the fun fact store. So first of all I thought okay maybe this can make me a millionaire I heard some pretty good stories about being a millionaire but then I thought okay yeah this is just a pile of money and this application is bigger than money. So maybe this can can give me the Nobel Prize you
know fun facts make you happy and what is a better medicine than happiness. So medicine would be a cool category for my Nobel Prize. But then I thought maybe this is even bigger than the Nobel Prize. Maybe this application can serve you in talking about performance problems of Java applications talking to a database. Now being a millionaire I don't think it it won't take me long it
will take me a few weeks and a Nobel Prize what to my surprise I missed out last October but some other people in the world were really unhappy when they found out that they didn't win a Nobel Prize well they think they should have done it. I'm not I will just take my next shot this October and serving you in talking about applications having performance problems with
databases I hope I I at least a bit today. So, basically, this was it. Uh you can rate my talk with uh with the QR code. Code, slides, LinkedIn are on the other on the other QR code. Time is up, but if you have questions and I will check the Slido as well, you can come to me. So, I will clear the stage for the next speaker,
but uh then I can still answer your questions from Slido or just face-to-face. For now, I will really thank you for your attention. Thank you.
More from this event
See all 34 talks →
Java: 30 Years and Beyond | Ana Maria Mihalceanu (EN)
39:47
Scaling Data in a Sovereign AI Platform | Johann Strauss & Markus Kett (EN)
49:29
Code Is Cheap. Software Isn’t. | Markus Eisele (EN)
47:23
Building a Digital Product Passport with Java and Cardano | Fabian Bormann (EN)
46:54