4 min read

How to load Spring boot thymeleaf views from database?

September 09, 2020

The problem with template files is that you need to make a new build when you add or modify a template file. This can be ideal for small projects. However, There may be cases where you may need to add, remove and update template file on a daily basis. 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 of the production scenario the application may be running on different servers and zones. So it not a bad idea to choose database as a template store. A typical spring boot application need to go through a couple of changes to achieve this. Let’s get through this.

First you need to setup a Table and appropriate JPA entities for the templates. My sample Entity looks like below.

@Data
@Entity
public class Template {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    @Column(unique = true)
    private String templateName;
    @Lob
    private String content;
}

Note that I have marked the templateName as unique. As template content can be quite long, I marked it as @Lob. JPA handles this column as a BLOB or a TEXT column.

I have defined a JpaRepository to query these entities.

@Repository
public interface TemplateRepository extends JpaRepository<Template, Integer> {
    
    Template findByTemplateName(String templateName);

}

Next you need to define a TemplateResolver implementation. Below is how I have done it.

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

By default, Spring boot will pickup any Bean of type ITemplateResolver and add it to its templateEngine auto configuration.

Notice that the constructor has three important details.

  • this.setResolvablePatterns(Collections.singleton("db-*"))

    • This informs Spring Boot to use this Resolver only for the template names that start with db-
  • this.setCacheable(true)

    • This line makes sure that the template not loaded from database every time. The cache will expire only after specified TTLMs
    • Setting this option to false may negatively impact application performance. Set this to false only when you know what you are doing.
  • this.setCacheTTLMs(5*60*1000L)

    • Configures the cached template to expire after 5 minutes. This method takes milli seconds. Note that small value would increase load to database. Too large value would mean that the template changes will not take effect quickly. It is up to you how much time you want to configure.

Similarly there are few other parameters you can tweak. But I will leave that part to you.

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 there DB templates, 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 type of templates can co-exist.

With a sample @Controller method, Lets test this setup.

    @RequestMapping("/welcome/{userType}")
    public String viewFromDbTemplate(@PathVariable String userType) {

        if (userType.equals("default")) {
            return "welcome-default";
        }
        return "db-welcome" + userType;
    }

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. Lets 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>

That’s it. We have done it.

If you have any questions or thoughts please comment below.

Raja Anbazhagan

About the author

Raja is a Software Engineer with over 7 years of experience in working with Enterprise Java applications. Lately, He is focused in cloud-based Java applications and serverless technologies. He spends his spare time in stackoverflow.

Browse Categories