Tuesday, 22 March 2016

Fun with Spring Boot auto-configuration

I favour configuration as straighforward as possible. Unfortunately Spring Boot is pretty much opposite as it employs lots of auto-configuration. Sometimes it is way too eager and initializes everything it stumbles upon.

If you are building fresh new project, you might be spared, but when converting pre-existing project or building something slightly unusual, you will very likely meet Spring Boot initialization failures.

There are two main groups of initialization sources

  • Libraries in classpath - Which might be dragged into classpath as a transitive dependency of library you really use.
  • Beans in application context - Spring Boot auto-configuration often supports only single resource of some kind. Database DataSource or JMS ConnectionFactory for example. When you have more than one, initializer gets confused and fails.
  • Combination of both - otherwise it would be too easy

Complete list of auto configuration classes is listed in documantation. For the record, I'm using Spring Boot 1.3.3 and Spring Framework 4.2.5

Having simple Spring Boot application like this...

@SpringBootApplication
public class AutoConfigBoom {

    @Bean
    @ConfigurationProperties(prefix = "datasource.ds1")
    DataSource ds1() {
        return DataSourceBuilder.create().build();
    }

    public static void main(String[] args) {
        SpringApplication.run(AutoConfigBoom.class, args);
    }
}
... and application.properties...
datasource.ds1.driverClassName=org.mariadb.jdbc.Driver
datasource.ds1.url=jdbc:mysql://localhost:3306/whatever?autoReconnect=true
datasource.ds1.username=hello
datasource.ds1.password=dolly
...if you happen to have JPA implementation like Hibernate in classpath, JPA engine will be initialized automatically. You might spot messages in logging output...
2016-03-22 18:45:55,560|main      |INFO |o.s.o.j.LocalContainerEntityManagerFactoryBean: Building JPA container EntityManagerFactory for persistence unit 'default'
2016-03-22 18:45:55,577|main      |INFO |o.hibernate.jpa.internal.util.LogHelper: HHH000204: Processing PersistenceUnitInfo [
 name: default
 ...]
2016-03-22 18:45:55,639|main      |INFO |org.hibernate.Version: HHH000412: Hibernate Core {5.1.0.Final}
2016-03-22 18:45:55,640|main      |INFO |org.hibernate.cfg.Environment: HHH000206: hibernate.properties not found
2016-03-22 18:45:55,641|main      |INFO |org.hibernate.cfg.Environment: HHH000021: Bytecode provider name : javassist
2016-03-22 18:45:55,678|main      |INFO |org.hibernate.annotations.common.Version: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
2016-03-22 18:45:55,686|main      |WARN |org.hibernate.orm.deprecation: HHH90000006: Attempted to specify unsupported NamingStrategy via setting [hibernate.ejb.naming_strategy]; NamingStrategy has been removed in favor of the split ImplicitNamingStrategy and PhysicalNamingStrategy; use [hibernate.implicit_naming_strategy] or [hibernate.physical_naming_strategy], respectively, instead.
2016-03-22 18:45:56,049|main      |INFO |org.hibernate.dialect.Dialect: HHH000400: Using dialect: org.hibernate.dialect.MySQL5Dialect
If it is undesired as it only slows down application start up, exclude particular initializers...
@SpringBootApplication(
  exclude = { HibernateJpaAutoConfiguration.class, JpaRepositoriesAutoConfiguration.class })
public class AutoConfigBoom {
  ...
}
...which is just shortcut for...
@Configuration
@EnableAutoConfiguration(
  exclude = { HibernateJpaAutoConfiguration.class, JpaRepositoriesAutoConfiguration.class })
@ComponentScan
public class AutoConfigBoom {
  ...
}

To make things more spicy, let's declare second DataSource. Let's assume that XA Transactions spanning multiple transactional resources are not required.

@SpringBootApplication
public class AutoConfigBoom {

    @Bean
    @ConfigurationProperties(prefix = "datasource.ds1")
    DataSource ds1() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "datasource.ds2")
    DataSource ds2() {
        return DataSourceBuilder.create().build();
    }

    public static void main(String[] args) {
        SpringApplication.run(AutoConfigBoom.class, args);
    }
}
If you have Hibernate JPA in classpath you will get...
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration': Injection of autowired dependencies failed; 
nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private javax.sql.DataSource org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration.dataSource; 
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'ds2' defined in com.example.boom.AutoConfigBoom: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceInitializer': Invocation of init method failed; 
nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: ds2,ds1
...because JPA engine is now confused which DataSource to use. If you really intend to use JPA EntityManager, then easiest solution is mark one DataSource with @Primary annotation...

    @Primary
    @Bean
    @ConfigurationProperties(prefix = "datasource.ds1")
    DataSource ds1() {
        return DataSourceBuilder.create().build();
    }

...otherwise exclude HibernateJpaAutoConfiguration.class, JpaRepositoriesAutoConfiguration.class from auto-configuration as shown above. @Primary annotation will also help you in case of...
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'ds1' defined in com.example.boom.AutoConfigBoom: Initialization of bean failed; 
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceInitializer': Invocation of init method failed; 
nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: ds2,ds1
Now, DataSourceInitializer cannot be simply excluded, but it can be turned off by adding...
spring.datasource.initialize=false
into application.properties, but you will get yet another initialization failure...
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration$JdbcTemplateConfiguration': Injection of autowired dependencies failed; 
nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private javax.sql.DataSource org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration$JdbcTemplateConfiguration.dataSource; 
nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: ds1,ds2

...which will force you to exclude DataSourceAutoConfiguration and also DataSourceTransactionManagerAutoConfiguration. Probably not worth it. Rather use @Primary to avoid this madness.

Small tip at last. We had few legacy apps, which were good candidates for Spring Bootification. Here goes typical Spring Boot wrapper I wrote to make upgrade/transition as seamless as possible. It reuses legacy external legacy properties file, which is now well covered in documantation. Part of upgrade was also logging migration to slf4j and logback (SLF4JBridgeHandler)

public final class LegacyAppWrapper {

    static {
        SLF4JBridgeHandler.removeHandlersForRootLogger();
        SLF4JBridgeHandler.install();
    }

    public static void main(String[] args) {
        // Do not do initialization tricks
        System.setProperty(
          "spring.datasource.initialize", "false");

        // Stop script does: curl -X POST http://localhost:8080/shutdown
        System.setProperty(
          "endpoints.shutdown.enabled", "true");

        // Instruct Spring Boot to use our legacy external property file
        String configFile = "file:" + System.getProperty("legacy.config.dir") + "/" + "legacy-config.properties";
        System.setProperty(
          "spring.config.location", configFile);

        ConfigurableApplicationContext context = SpringApplication.run(LegacyAppSpringBootConfig.class, args); // embedded http server blocks until shutdown
    }
}

Happy auto-configuration exclusions

Friday, 18 March 2016

Join unrelated entities in JPA

With SQL, you can join pretty much any two tables on almost any columns that have compatible type. This is not the possible in JPA as it relies heavily on relation mapping.

JPA relation between two entities must be declared, usually using @OnToMany or @ManyToOne or another annotation on some JPA entity's field. But sometimes you cannot simply introduce new relation into your existing domain model as it can also bring all sorts of troubles, like unnecessary fetches or lazy loads. Then ad hoc joining comes very handy.

JPA 2.1 introduced ON clause support, which is useful but not ground breaking. It only allows to use additional joining condition to one that implicitly exists because of entity relation mapping.

To cut story short, JPA specification still does not allow ad hoc joins on unrelated entities, but fortunately both two most used JPA implementation can do it now.

Of course, you still have to map columns (most likely numeric id columns) using @Id or @Column annotation, but you do not have to declare relation between entities to be able to join them.

EclipseLink since version 2.4 - User Guide and Ticket

Hibernate starting with version 5.1.0 released this February - Announcement and Ticket

Using this powerful tool, we can achieve unimaginable.

@Entity
@Table(name = "CAT")
class Cat {

    @Id
    @Column(name = "NAME")
    private String name;

    @Column(name = "KITTENS")
    private Integer kittens;
}

@Entity
@Table(name = "INSURANCE")
class Insurance {

    @Id
    @Column(name = "NUMBER")
    private Integer number;

    @Temporal(TemporalType.DATE)
    private Date startDate;
}

For example to join number of kittens your cat has with your insurance number!
String jpaql="SELECT Cat c JOIN Insurance i ON c.kittens = i.number";
entityManager.createQuery(jpaql);

If you try this with Hibernate version older than 5.1.0 you will get QuerySyntaxException
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: Path expected for join!

Happy ad hoc joins!