Thymeleaf views from database in Spring Boot
In this post, We will learn how to load thymeleaf views from the database without restarting the spring boot application.
Problems with thymeleaf view files
You have seen how to load thymeleaf templates for views or HTML emails. The problem with thymeleaf views is that you need to make a new build when you make changes to templates. This can be ideal for small projects. However, there may be cases where you need to add, remove, and update the template file daily. In that case, it would be practical to move the templates out of the build.
My initial thought for this problem was to keep a directory full of templates on the server where the application would run. But in most production scenarios, the application may be running on different servers and zones. So it is not a bad idea to choose a database as a template store. A typical spring boot application needs to go through a couple of changes to achieve this. Let’s get through this.
First, you need to set up a database Table and appropriate JPA entities for the views.
@Data
@Entity
public class Template {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Column(unique = true)
private String templateName;
@Lob
private String content;
}
Code language: Java (java)
Note that I have marked the templateName as unique. As template content can be quite long, So I marked it as
@Lob
. JPA handles this column as a BLOB or a TEXT column in the database.
I have defined a JpaRepository
to query these entities.
@Repository
public interface TemplateRepository extends JpaRepository<Template, Integer> {
Template findByTemplateName(String templateName);
}
Code language: Java (java)
Implementing StringTemplateResolver
In addition to that, you need to define a TemplateResolver implementation to load views from different locations. In other words, You need to tell Spring Boot to load template views from the database.
@Component
public class DatabaseTemplateResolver extends StringTemplateResolver {
private static final Logger logger = getLogger(DatabaseTemplateResolver.class);
final
private TemplateRepository templateRepository;
public DatabaseTemplateResolver(TemplateRepository templateRepository) {
this.templateRepository = templateRepository;
this.setResolvablePatterns(Collections.singleton("db-*"));
this.setCacheTTLMs(5*60*1000L);
this.setCacheable(true);
}
@Override
protected ITemplateResource computeTemplateResource(IEngineConfiguration configuration,
String ownerTemplate,
String templateName,
Map<String, Object> templateResolutionAttributes) {
logger.info("Loading template named {} from DB", templateName);
Template template = templateRepository.findByTemplateName(templateName);
if (template == null) {
return null;
}
return super
.computeTemplateResource(configuration, ownerTemplate, template.getContent(), templateResolutionAttributes);
}
}
Code language: Java (java)
Subsequently, Spring boot will pick up any Bean of type ITemplateResolver and add it to its templateEngine
auto-configuration.
The constructor has three main details.
this.setResolvablePatterns(Collections.singleton("db-*"))
- Informs Spring Boot to use this Resolver only for the template names that start with
db-
- Informs Spring Boot to use this Resolver only for the template names that start with
this.setCacheable(true)
- This line makes sure that the template not loaded from the database every time. The cache will expire only after specified
TTLMs
- Be careful with this setting. That is to say that this may negatively impact application performance.
- This line makes sure that the template not loaded from the database every time. The cache will expire only after specified
this.setCacheTTLMs(5*60*1000L)
- The number of milliseconds for the cache lifecycle. Keep it too small and DB will get overloaded and, keep it large and your changes in the DB will not be updated soon enough.
In addition to that, there are few other parameters you can tweak. However, I will leave that part to you.
Database Entries for Thymeleaf Templates
I have generated a bunch of test entries in the DB.
id | template_name | content |
---|---|---|
1 | db-welcome-bronze | <h1> Hello Bronze user</h1> |
2 | db-welcome-silver | <h1> Hello Silver user</h1> |
3 | db-welcome-gold | <h1> Hello Gold user</h1> |
4 | db-welcome-platinum | <h1> Hello Platinum user</h1> |
Along with these database entries for thymeleaf views, I have made a file-based template called welcome-default.html
. Its content is just <h1>Hello from file</h1>
. This template will demonstrate how both types of view resolvers can coexist.
With a sample @Controller
method, Let’s test the thymeleaf templates from the database.
@RequestMapping("/welcome/{userType}")
public String viewFromDbTemplate(@PathVariable String userType) {
if (userType.equals("default")) {
return "welcome-default";
}
return "db-welcome" + userType;
}
Code language: Java (java)
Testing views from Database
For the above @RequestMapping
, URL /welcome/default
should render template welcome-default.html
. And the URLs like /welcome/platinum
will result in rendering views from DB. Let’s test these.
$ curl -X GET "http://localhost:8080/welcome/gold"
HTTP/1.1 200
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
Transfer-Encoding: chunked
Date: Wed, 09 Sep 2020 15:45:37 GMT
Keep-Alive: timeout=60
Connection: keep-alive
<h1> Hello Gold user</h1>
$ curl -X GET "http://localhost:8080/welcome/default"
HTTP/1.1 200
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
Transfer-Encoding: chunked
Date: Wed, 09 Sep 2020 15:45:37 GMT
Keep-Alive: timeout=60
Connection: keep-alive
<h1>Hello from file</h1>
Code language: Java (java)
Conclusion
To summarize, We learned how to load thymeleaf views from the database. If you liked this article, you may also be interested in the following write-ups.
You can check out this example from our GitHub Repository
Please share the git URL for the project -“Thymeleaf views from database in Spring Boot”
Thanks
Hey, Thanks for pointing it out. I have updated the GitHub URL in the post.
Thank you very much Raj..
Similar like this do we have loading FreeMarker template from DB? if yes plz share the URL.
Thanks.
Oh man, great thx! It’s working perfectly!
This tutorial just the one in the google 😉 Thx
Hello,
This is the exact use case I am looking for.
I used the code that you have shared, but the method “computeTemplateResource” is not getting triggered.
Can you please help here.
Thanks,
Flora
It is hard to give an answer without looking at your code. But the way to said it, You may be using the template lookup wrong. Take a look at the line
this.setResolvablePatterns(Collections.singleton("db-*"));
. This means that only the templates with their names starting with “db-” will be taken care of by this resolver. make sure your template matches this name pattern.