SpringBoot: Configuring Spring DataSource for Tests - java

I have a SpringBoot app.
I have created this test:
#ContextConfiguration(classes={TestConfig.class})
#RunWith(SpringRunner.class)
#SpringBootTest
public class SuncionServiceITTest {
#Test
public void should_Find_2() {
// TODO
}
}
where
#Configuration
#EnableJpaRepositories(basePackages = "com.plats.bruts.repository")
#PropertySource("local-configuration.properties")
#EnableTransactionManagement
#SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class TestConfig {
}
and local configuration.properties:
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa
but when I run the test. I got this error:
Caused by:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
bean named 'entityManagerFactory' available
I also tried with:
#EnableJpaRepositories(basePackages = "com.plats.bruts.repository", entityManagerFactoryRef="emf")
but then I have the error:
Caused by:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
bean named 'emf' available

Looks like you are missing below starter dependency. This starter dependency has all the necessary dependencies needed to configure the jpa repositories.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

This is an approach of how to configure several data sources in one application. I have tested it for spring-webmvc and graphql-java, but I think it can be useful for spring-boot as well.
Configure several data sources with spring-data-jpa
For each database we should enable JPA repositories and specify the base packages for the corresponding interfaces. Also for each database we should specify the entity manager factory and the base packages for corresponding entities, as well as the transaction manager and the data source, of course.
To do this, we'll include in our application one configuration class for data JPA and three inner classes for each database. See the Simple GraphQL implementation.
DataJpaConfig.java
package org.drakonoved.graphql;
#Configuration
#PropertySource(value = "classpath:resources/application.properties", encoding = "UTF-8")
public class DataJpaConfig {
private final String basePackage = "org.drakonoved.graphql";
#EnableJpaRepositories(
basePackages = basePackage + ".repository.usersdb",
entityManagerFactoryRef = "usersdbEntityManagerFactory",
transactionManagerRef = "usersdbTransactionManager")
public class UsersDBJpaConfig {
#Bean("usersdbEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean usersDBEntityManagerFactoryBean(
#Value("${datasource.usersdb.script}") String script) {
return createEntityManagerFactoryBean(
script, "usersdb", basePackage + ".dto.usersdb");
}
#Bean("usersdbTransactionManager")
public PlatformTransactionManager usersDBTransactionManager(
#Qualifier("usersdbEntityManagerFactory")
LocalContainerEntityManagerFactoryBean factoryBean) {
return new JpaTransactionManager(factoryBean.getNativeEntityManagerFactory());
}
}
#EnableJpaRepositories(
basePackages = basePackage + ".repository.rolesdb",
entityManagerFactoryRef = "rolesdbEntityManagerFactory",
transactionManagerRef = "rolesdbTransactionManager")
public class RolesDBJpaConfig {
#Bean("rolesdbEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean rolesDBEntityManagerFactoryBean(
#Value("${datasource.rolesdb.script}") String script) {
return createEntityManagerFactoryBean(
script, "rolesdb", basePackage + ".dto.rolesdb");
}
#Bean("rolesdbTransactionManager")
public PlatformTransactionManager rolesDBTransactionManager(
#Qualifier("rolesdbEntityManagerFactory")
LocalContainerEntityManagerFactoryBean factoryBean) {
return new JpaTransactionManager(factoryBean.getNativeEntityManagerFactory());
}
}
#EnableJpaRepositories(
basePackages = basePackage + ".repository.usersnrolesdb",
entityManagerFactoryRef = "usersnrolesdbEntityManagerFactory",
transactionManagerRef = "usersnrolesdbTransactionManager")
public class UsersNRolesDBJpaConfig {
#Bean("usersnrolesdbEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean usersNRolesDBEntityManagerFactoryBean(
#Value("${datasource.usersnrolesdb.script}") String script) {
return createEntityManagerFactoryBean(
script, "usersnrolesdb", basePackage + ".dto.usersnrolesdb");
}
#Bean("usersnrolesdbTransactionManager")
public PlatformTransactionManager usersNRolesDBTransactionManager(
#Qualifier("usersnrolesdbEntityManagerFactory")
LocalContainerEntityManagerFactoryBean factoryBean) {
return new JpaTransactionManager(factoryBean.getNativeEntityManagerFactory());
}
}
//////// //////// //////// //////// //////// //////// //////// ////////
private LocalContainerEntityManagerFactoryBean createEntityManagerFactoryBean(
String script, String dbname, String packagesToScan) {
var factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.setName(dbname)
.addScript(script)
.build());
factoryBean.setPersistenceUnitName(dbname + "EntityManagerFactory");
factoryBean.setPackagesToScan(packagesToScan);
factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
var properties = new HashMap<String, Object>();
properties.put("hibernate.hbm2ddl.auto", "none");
properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
factoryBean.setJpaPropertyMap(properties);
return factoryBean;
}
}

I prefer to use following approach (i don't like to create own bean configurator). As #svr correctly noticed (see previous answer) you don't add starter package for beans auto configure.
I usually create different profiles: for local app running, for dev, prod and finally last one for tests. In my test profile (application-functests.yml) i configure all settings that needs for my functional tests (datasources, hibernate, cache and so on), i.e. my application-functests.yml of one of my projects:
spring:
http:
encoding:
charset: UTF-8
enabled: true
profiles: functests
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
enable_lazy_load_no_trans: true
naming:
physical-strategy: com.goodt.drive.orgstructure.application.utils.SnakePhysicalNamingStrategy
hibernate:
ddl-auto: none
database-platform: org.hibernate.dialect.PostgreSQL9Dialect
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/monitor_service_functests
username: developer
password: 123
sql-script-encoding: UTF-8
liquibase:
change-log: classpath:db/changelog/changelog.xml
I have only specify profile for running test, therefore all of my functional tests are using functests profile, i.e.:
#SpringBootTest
#ActiveProfiles("functests")
public class TestEventRepository extends FunctionalTestBase {
#Test
public void testGetAll() {
Iterable<EventEntity> eventIterable = dbContext.getEventDataSource().findAll();
Iterator<EventEntity> it = eventIterable.iterator();
List<EventEntity> actualEvents = new ArrayList<>();
while (it.hasNext()) {
actualEvents.add(it.next());
}
List<EventCheckData> expectedEvents = new ArrayList<>() {{
add(new EventCheckData(1L, 1L, "body 1", 1L, 1L));
add(new EventCheckData(2L, 2L, "body 2", 2L, 2L));
add(new EventCheckData(3L, 3L, "body 3", 3L, 1L));
add(new EventCheckData(4L, 1L, "body 4", 2L, 1L));
add(new EventCheckData(5L, 2L, "body 5", 1L, 2L));
}};
EventSimpleChecker.check(expectedEvents, actualEvents);
}
}
In my code example i have base test class - FunctionalTestBase which is used for interact with liquibase (toggle it to apply migrations)

Related

Use main application datasource in cucumber test module - Spring-boot application

I was trying to insert data into database in my cucumber tests module in spring-boot application.
When start spring app with test profile (mvn spring-boot:run -Dspring-boot.run.profiles=test) it start up the application and run properly. The issue is during cucumber test execution when try to setup the datasource (as pointed out ** line in the code below) it comes as null. So should I setup the datasource again? If so how.
It's not cucumber test related issue, The issue is I can't access the datasource which have set in the main app.
Below is the code
#ContextConfiguration(classes = MainApp.class, loader = SpringBootContextLoader.class)
#ActiveProfiles("test")
#Configuration
#PropertySource({"classpath:create-sql.xml"})
public class TestHelper {
#Value("${CreateSql}")
private String CreateSql;
#Autowired
private SqlQueryBuilder sqlQueryBuilder;
#Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
#Autowired
private UserPreferenceFormatter formatter;
#Autowired
private DataSource dataSource;
public static void getDataList() throws IOException {
MapSqlParameterSource sqlParamSource = new MapSqlParameterSource();
sqlQueryBuilder = new SqlQueryBuilder();
jdbcTemplate = new NamedParameterJdbcTemplate(dataSource); ****
String parsedSql = sqlQueryBuilder.parseSql(CreateSql,null,null,null);
List<DataSummary> dataSummaries = jdbcTemplate.query(parsedSql, sqlParamSource, new DataSummaryRowMapper(null,formatter));
}
application-test.yml file under resources folder with all spring datasources within test module
app-db-url: jdbc:oracle:....
app-db-user: USERNAME
spring:
datasource:
password: PWD
I went through below solution as well
Solution-1
Solution-2
Deployment module app-config.yml
....
data:
# Database
app-db-url : ##app-db-url##
app-db-user: ##app-db-user##
......
It looks like you are missing code that defines that DataSource bean.
You should have something like this:
#Configuration
public class DataSourceConfig {
#Bean
public DataSource getDataSource() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("org.h2.Driver");
dataSourceBuilder.url("jdbc:h2:mem:test");
dataSourceBuilder.username("SA");
dataSourceBuilder.password("");
return dataSourceBuilder.build();
}
}
or something like that:
#Bean
public DataSource getDataSource() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.username("SA");
dataSourceBuilder.password("");
return dataSourceBuilder.build();
}
and the rest of the propertied can go into a property file.

Spring Boot multiple configurations files with same names and same properties names

I am trying to setup a Spring Boot monolithic app which should behave like a microservice and I have some issues to manage configurations (.properties).
This is how I organized the project (resources folder) :
As you can see there are common properties files : application.properties,application-dev.properties and application-prod.properties these properties should be shared by all sub-properties and they can eventually be overridden.
Each service has its own data source url and it also depending on the active profile : H2 for dev and MySQL for prod.
Here an exemple of how I manage configurations :
Contents of common application.properties:
spring.profiles.active=dev
spring.config.additional-location=classpath:productservice/, classpath:userservice/,classpath:financeservice/, classpath:securityservice/ #I am not sure this works...
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.database=default
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.validation-mode=none
Contents of common application-dev.properties:
spring.datasource.driver-class-name=org.h2.Driver
spring.h2.console.enabled=true
spring.h2.console.path=/db
spring.h2.console.settings.trace=false
spring.h2.console.settings.web-allow-others=false
spring.datasource.username=sa # We override data source username and paswword
spring.datasource.password=
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
Contents of common application-prod.properties:
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
Now for specific services :
productservice :
Content of application.properties:
spring.liquibase.change-log=classpath:productservice/db/changelog/db.changelog-master.xml
spring.liquibase.default-schema=productservicedb
spring.liquibase.check-change-log-location=true
Content of application-dev.properties:
spring.datasource.url=jdbc:h2:mem:productservicedb;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS productservicedb;MV_STORE=FALSE;MVCC=FALSE
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
Content of application-prod.properties:
spring.datasource.url=jdbc:mysql://localhost:3306/productservicedb?useSSL=false
And for userservice I have :
Content of application.properties :
spring.liquibase.change-log=classpath:userservice/db/changelog/db.changelog-master.xml
spring.liquibase.default-schema=userservice
spring.liquibase.check-change-log-location=true
Content of application-dev.properties :
spring.datasource.url=jdbc:h2:mem:userservicedb;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS userservicedb;MV_STORE=FALSE;MVCC=FALSE
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
Content of application-prod.properties :
spring.datasource.url=jdbc:mysql://localhost:3306/userservicedb
And so on ... for other services.
To configure each data source I proceed like this :
productservice :
1- Retrieve properties from environment :
#Configuration( "productServiceProperties" )
#Getter
#Setter
#PropertySource( value = { "classpath:productservice/application.properties",
"classpath:productservice/application-${spring.profiles.active}.properties" } )
public class ProductServiceProperties {
#Autowired//I want `Environment` to contain properties from common properties + files in #PropertySource above
private Environment environment;
// ========DATASOURCE PROPERTIES=========
private String datasourceDriverClass;
private String datasourceUsername;
private String dataSourcePassword;
private String dataSourceUrl;
// ========LIQUIBASE PROPERTIES==========
private String liquibaseChangeLog;
private String liquibaseDefaultSchema;
#PostConstruct
public void init() {
this.datasourceDriverClass = environment.getProperty( "spring.datasource.driver-class-name" );
datasourceUsername = environment.getProperty( "spring.datasource.username" );
dataSourcePassword = environment.getProperty( "spring.datasource.password" );
dataSourceUrl = environment.getProperty( "spring.datasource.url" );
liquibaseChangeLog = environment.getProperty( "spring.liquibase.change-log" );
liquibaseDefaultSchema = environment.getProperty( "spring.liquibase.default-schema" );
log.debug( "Initialisation {} ", datasourceDriverClass );
}
}
2- Inject them in the DB configuration :
#Configuration
#EnableJpaRepositories( basePackages = "com.company.product.repository", entityManagerFactoryRef = "productServiceEntityManager", transactionManagerRef = "productServiceTransactionManager", considerNestedRepositories = true )
#EnableTransactionManagement( proxyTargetClass = false )
public class ProductServiceDBConfig {
private static final String packageToScan = "com.company.product.models";
#Autowired
private ProductServiceProperties productServiceProperties;
#Bean( name = "productServiceDataSource" )
public DataSource productServiceDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName( productServiceProperties.getDatasourceDriverClass() );
dataSource.setUrl( productServiceProperties.getDataSourceUrl() );
dataSource.setUsername( productServiceProperties.getDatasourceUsername() );
dataSource.setPassword( productServiceProperties.getDataSourcePassword() );
return dataSource;
}
#Bean
public JpaVendorAdapter productServiceVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setGenerateDdl( false );
hibernateJpaVendorAdapter.setShowSql( true );
return hibernateJpaVendorAdapter;
}
#Bean( name = "productServiceEntityManager" )
public LocalContainerEntityManagerFactoryBean productServiceEntityManager() {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource( productServiceDataSource() );
emf.setPackagesToScan( packageToScan );
emf.setJpaVendorAdapter( productServiceVendorAdapter() );
return emf;
}
#Bean( name = "productServiceTransactionManager" )
public PlatformTransactionManager productServiceTransactionManager() {
JpaTransactionManager productTransactionManager = new JpaTransactionManager();
productTransactionManager.setEntityManagerFactory( productServiceEntityManager().getObject() );
return productTransactionManager;
}
#Bean
public SpringLiquibase productServiceLiquibase() {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDataSource( productServiceDataSource() );
liquibase.setChangeLog( productServiceProperties.getLiquibaseChangeLog() );
liquibase.setDefaultSchema( productServiceProperties.getLiquibaseDefaultSchema() );
return liquibase;
}
}
userservice :
1-
#Configuration( "userServiceProperties" )
#Getter
#Setter
#PropertySource( value = { "classpath:userservice/application.properties",
"classpath:userservice/application-${spring.profiles.active}.properties" } ) //I want properties from properties file source above
public class UserServiceProperties {
#Autowired //I want `Environment` to contain properties from common properties + files in #PropertySource above
private Environment environment;
// ========DATASOURCE PROPERTIES=========
private String datasourceDriverClass;
private String datasourceUsername;
private String dataSourcePassword;
private String dataSourceUrl;
// ========LIQUIBASE PROPERTIES==========
private String liquibaseChangeLog;
private String liquibaseDefaultSchema;
#PostConstruct
public void init() {
datasourceDriverClass = environment.getProperty( "spring.datasource.driver-class-name" );
datasourceUsername = environment.getProperty( "spring.datasource.username" );
dataSourcePassword = environment.getProperty( "spring.datasource.password" );
dataSourceUrl = environment.getProperty( "spring.datasource.url" );
liquibaseChangeLog = environment.getProperty( "spring.liquibase.change-log" );
liquibaseDefaultSchema = environment.getProperty( "spring.liquibase.default-schema" );
}
}
2-
#Configuration
#EnableJpaRepositories( basePackages = "com.company.user.repository", entityManagerFactoryRef = "userServiceEntityManager", transactionManagerRef = "userServiceTransactionManager", considerNestedRepositories = true )
#EnableTransactionManagement( proxyTargetClass = false )
public class UserServiceDBConfig {
private static final String packageToScan = "com.company.user.models";
#Autowired
private UserServiceProperties userServiceProperties;
#Bean( name = "userServiceDataSource" )
public DataSource userServiceDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName( userServiceProperties.getDatasourceDriverClass() );
dataSource.setUrl( userServiceProperties.getDataSourceUrl() );
dataSource.setUsername( userServiceProperties.getDatasourceUsername() );
dataSource.setPassword( userServiceProperties.getDataSourcePassword() );
return dataSource;
}
#Bean
public JpaVendorAdapter userServiceVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setGenerateDdl( false );
hibernateJpaVendorAdapter.setShowSql( true );
return hibernateJpaVendorAdapter;
}
#Bean( name = "userServiceEntityManager" )
public LocalContainerEntityManagerFactoryBean userServiceEntityManager() {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource( userServiceDataSource() );
emf.setPackagesToScan( packageToScan );
emf.setJpaVendorAdapter( userServiceVendorAdapter() );
return emf;
}
#Bean( name = "userServiceTransactionManager" )
public PlatformTransactionManager userServiceTransactionManager() {
JpaTransactionManager userTransactionManager = new JpaTransactionManager();
userTransactionManager.setEntityManagerFactory( userServiceEntityManager().getObject() );
return userTransactionManager;
}
#Bean
public SpringLiquibase userServiceLiquibase() {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDataSource( userServiceDataSource() );
liquibase.setChangeLog( userServiceProperties.getLiquibaseChangeLog() );
liquibase.setDefaultSchema( userServiceProperties.getLiquibaseDefaultSchema() );
return liquibase;
}
}
The problem is, as I #Autowired Environment to get my properties and all properties have same names, some properties always take precedence over other properties. E.g. the property spring.datasource.url contains the same value in both services.
I use Environment and not #Value neither #ConfigurationProperties because it gets properties basing on the active profile and that exactly what I want.
My questions are :
Is it possible to configure Environment to get properties from the configuration class where it is injected?
If not, is there a simple way to achieve what I want to do?
Should I configure multiple application contexts (one per service) to isolate each Environment variable from other? (I am not sure this would work)
Thanks a lot (and sorry for the length of this post)
I solve the problem myself with the suggestion of Andy Wilkinson (#ankinson) on Twitter.
We can't have many properties files with same name. So I had to rename my sub configurations files like this :
product.properties
product-dev.properties
product-prod.properties
And so on...

How to run standalone Hibernate 4 SchemaExport with Java Spring config

We have a Java Spring project using JPA with Hibernate 4 for ORM. We exclusively use Java config, so we don't have any hibernate.properties or persistence.xml mapping files.
We're also using Spring's Jsr310JpaConverters and some custom attribute converters implementing javax.persistence.AttributeConverter. The custom converters are picked up automatically by the package scan.
Other than that, our setup is fairly standard, basically just
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackageClasses = BasePackageMarker.class, repositoryBaseClass = InternalRepositoryImpl.class)
public class JpaConfig {
// values loaded from property file
public Properties jpaProperties() {
Properties jpaProperties = new Properties();
jpaProperties.setProperty(Environment.DIALECT, dialect);
jpaProperties.setProperty(Environment.HBM2DDL_AUTO, getHbm2ddlAuto());
// ...
return jpaProperties;
}
#Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setDriverClassName(driver);
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
// ...
return new HikariDataSource(config);
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setPackagesToScan(
BasePackageMarker.class.getPackage().getName(),
Jsr310JpaConverters.class.getPackage().getName()
);
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManagerFactoryBean.setJpaProperties(jpaProperties());
return entityManagerFactoryBean;
}
// ...
}
I'm looking for a way to run Hibernate's SchemaExport to create our database schema, without running Tomcat or the Spring application, with the same configuration as starting the application with hbm2ddl=create, specifically finding all entity classes and attribute converters. I'd like to run it with Maven, but I can figure that part out once I know where to start.
I've found many outdated answers and libraries for Hibernate 3 or for XML config, nothing seems to work for our setup. I'm sure the answer is already out there, but at this point I'm quite confused and don't know what to try. Any solutions or pointers?
I think what you need go with AnnotationConfiguration():
In hibernate, database connection can also be achieved without using hibernate.cfg.xml. In hibernate annotation, there is a class named as AnnotationConfiguration. AnnotationConfiguration provides the method to configure database properties.
There are different methods of AnnotationConfiguration like .addAnnotatedClass, .addProperties etc. There is .configure() methods which seeks hibernate.cfg, We need not to use .configure() if we are not intended to use hibernate.cfg.
suppose I have entity User
package com.persistence;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name="user")
public class User {
#Id
#GeneratedValue
private int id;
#Column(name="name")
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
HibernateUtil class
public class HibernateUtil {
private static final SessionFactory concreteSessionFactory;
static {
try {
Properties prop= new Properties();
prop.setProperty("hibernate.connection.url", "jdbc:mysql://localhost:3306/hibernate");
prop.setProperty("hibernate.connection.username", "root");
prop.setProperty("hibernate.connection.password", "");
prop.setProperty("dialect", "org.hibernate.dialect.MySQLDialect");
prop.setProperty("hbm2ddl.auto", "create");
concreteSessionFactory = new AnnotationConfiguration()
.addPackage("com.persistence")
.addProperties(prop)
.addAnnotatedClass(User.class)
.buildSessionFactory();
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
public static Session getSession()
throws HibernateException {
return concreteSessionFactory.openSession();
}
public static void main(String... args){
Session session=getSession();
session.beginTransaction();
User user=(User)session.get(User.class, new Integer(1));
System.out.println(user.getName());
session.close();
}
}
Here's my solution based on Bhushan Uniyal's answer. Using reflection to find our entities and attribute converters.
public static void main(String... args) {
Properties prop = new Properties();
prop.setProperty("hibernate.connection.url", "jdbc:mysql://127.0.0.1:3306/db");
prop.setProperty("hibernate.connection.username", "user");
prop.setProperty("hibernate.connection.password", "xxxx");
prop.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
prop.setProperty("hibernate.hbm2ddl.auto", "create");
Configuration cfg = new Configuration()
.addPackage("com.persistence")
.addProperties(prop);
Reflections basePackageReflections = new Reflections(BasePackageMarker.class.getPackage().getName());
for (Class<?> entityClass : basePackageReflections.getTypesAnnotatedWith(Entity.class)) {
System.out.println("Adding entity class: " + entityClass.getSimpleName());
cfg.addAnnotatedClass(entityClass);
}
for (Class<? extends AttributeConverter> attributeConverter : basePackageReflections.getSubTypesOf(AttributeConverter.class)) {
System.out.println("Adding attribute converter: " + attributeConverter.getSimpleName());
cfg.addAttributeConverter(attributeConverter);
}
cfg.addAttributeConverter(Jsr310JpaConverters.LocalDateConverter.class);
cfg.addAttributeConverter(Jsr310JpaConverters.LocalDateTimeConverter.class);
cfg.addAttributeConverter(Jsr310JpaConverters.LocalTimeConverter.class);
cfg.addAttributeConverter(Jsr310JpaConverters.InstantConverter.class);
new SchemaExport(cfg).create(true, true);
}

Spring boot unable to read from profile specific yaml file

I was playing around with spring boot and attempted to create a profile specific configuration file. I called it application-local.yml.
And added:
spring:
profiles:
active: local
mysql:
db:
url: jdbc:mysql://localhost:3306/db?serverTimezone=UTC
driverClassName: com.mysql.jdbc.Driver
username: root
password:
hibernate:
dialect: org.hibernate.dialect.MySQL5Dialect
show_sql: false
server:
port: 8080
And in my DatabaseConfig.java file I attempt to read from application-local.yml and configure the database:
#Configuration
#EnableTransactionManagement
public class DatabaseConfig {
#Value("${spring.mysql.db.url}")
private String url;
#Value("${spring.mysql.db.username}")
private String userName;
#Value("${spring.mysql.db.password}")
private String password;
#Value("${spring.mysql.db.driverClassName}")
private String driverClassName;
//hibernate
#Value("${hibernate.dialect}")
private String hibernateDialect;
#Value("${hibernate.show_sql}")
private String hibernateShowSql;
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl(url);
dataSource.setDriverClassName(driverClassName);
dataSource.setUsername(userName);
dataSource.setPassword(password);
return dataSource;
}
#Bean(name="entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean =
new LocalContainerEntityManagerFactoryBean();
localContainerEntityManagerFactoryBean.setDataSource(dataSource());
localContainerEntityManagerFactoryBean.setPackagesToScan("xxxx");
JpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
localContainerEntityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter);
localContainerEntityManagerFactoryBean.setJpaProperties(hibernateProperties());
return localContainerEntityManagerFactoryBean;
}
#Bean
public PlatformTransactionManager platformTransactionManager() {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(entityManagerFactoryBean().getObject());
return jpaTransactionManager;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", hibernateDialect);
properties.put("hibernate.show_sql", hibernateShowSql);
return properties;
}
}
Now I get the error it's unable to
Error creating bean with name 'databaseConfig': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'spring.mysql.db.url' in value "${spring.mysql.db.url}"
but when I rename my property file to application.yml. It works perfectly fine.
I also attempted to run from the terminal using gradle command: ./gradlew -Dspring.profiles.active=local bootRun but I get the same error. It only works when I refactor the YAML file to application.yml. What am I doing wrong? I intend to have 3 profiles such as local, dev and prod.
Either add the following in your gradle file
bootRun {
profile: local
}
or try
./gradlew bootRun -Dspring.profiles.active=local
I had the same issue. I resolved it by adding
Spring:
profiles: local
Please make sure its indented properly.

Spring Hibernate H2 Junit testing - how to load schema on start

I am trying to develop tests for my application (I found out about the tests very late...) and I am stuck and the basic configuration. I have googled through many examples and none of them satisfied me and frankly left me a bit confused.
What I am trying to achieve is to load an import.sql on start of the test (which is a dump file from existing MySQL schema) and load it into H2 database.
Here is the hibernate config file:
#Configuration
#EnableTransactionManagement
#ComponentScan({ "kamienica.feature" })
public class HibernateTestConfiguration {
#Autowired
ApplicationContext applicationContext;
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(new String[] { "kamienica" });
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean(name = "dataSource")
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;"
+ "INIT=CREATE SCHEMA IF NOT EXISTS kamienica;DB_CLOSE_ON_EXIT=FALSE");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
properties.put("hibernate.hbm2ddl.auto", "update");
// this is where I tried to load script the first time:
// properties.put("hibernate.hbm2ddl.import_files", "kamienica.sql");
return properties;
}
#Bean
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory s) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(s);
return txManager;
}
}
Everytime I start a test I get a message that:
org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata INFO:
HHH000262: Table not found: apartment
And I get empty/null values when trying to retrieve anything
I have tried to load sql file in the hibernate config (via hibernate properties) as well as in superclass which all my test classes are planned to extend:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { HibernateTestConfiguration.class })
public class BaseTest {
private EmbeddedDatabase db;
#Autowired
private SessionFactory sessionFactory;
//second attempt to load sql file
#Before
public void setUp() throws Exception {
db = new EmbeddedDatabaseBuilder().addScript("import.sql").build();
}
#After
public void tearDown() throws Exception {
SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
SessionFactoryUtils.closeSession(sessionHolder.getSession());
}
}
How can I load sql file and prepare the H2 database to perform the tests?
I hope this spring boot approach will help you. First create a resources directory (classpath for springboot)in the src/test directory at the root of your project.
In this directory, you will start placing your fixture SQL data files named say data.sql .
Then, create a application.properties file on the same level (same directory see screenshot). This file should be populated as shown here:
spring.datasource.url = jdbc:h2:~/test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
#spring.datasource.url: jdbc:mysql://localhost:3306/yourDB
#spring.datasource.username = root
#spring.datasource.password =
# Hibernate
hibernate.show_sql: true
#hibernate.dialect: org.hibernate.dialect.MySQL5Dialec
spring.jpa.hibernate.ddl-auto=none
Screenshot:
Now your tester method.
#RunWith(SpringJUnit4ClassRunner.class)
....
#Autowired
private DataSource ds; //your application.properties
#Autowired
private WebApplicationContext context;
private static boolean loadDataFixtures = true;
private MockMvc mockMvc;
....
#Before
public void setupMockMvc() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
#Before
public void loadDataFixtures() {
if (loadDataFixtures) {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator(context.getResource("classpath:/data.sql"));
DatabasePopulatorUtils.execute(populator, ds);
loadDataFixtures = false;
}
}
#Test
public void yourmethod() {
assertEquals(3, repository.count()); //example
}
Without any output or the complete stacktrace, the only I can suggest you is:
You aren't showing any #Test method. How are you getting that error?
Is your file import.sql in src/test/resources folder? (note the test path)
Is your sql script well formated? Have you tried to run once exported? Could you post the part of the sql script wich creates the apartment table ?
If all are true, maybe the problem is not about loading the sql but how it's used, or the content of the script, or the name of the tables, etc...
After a long 'investigation' I have concluded that the problem was hidden somewhere in he DBUnit, TestNG setup.
I decided to keep it simple and switched to JUnit tests.
In case others might have similar problems here is the config file that works for me:
#Configuration
#EnableTransactionManagement
#ComponentScan({ "kamienica.feature" })
public class JUnitConfig {
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(new String[] { "kamienica" });
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean(name = "dataSource")
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:mem:kamienica;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
properties.put("hibernate.hbm2ddl.auto", "create");
return properties;
}
#Bean
public HibernateTransactionManager transactionManager(SessionFactory s) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(s);
return txManager;
}
}
All is needed now is to insert import.sql file in the resources folder.
I also found out that each insert statements must be in one line no matter how long it is, otherwise it won't be loaded.
Finally a simple test class:
#ContextConfiguration(classes = { JUnitConfig.class })
#RunWith(SpringJUnit4ClassRunner.class)
public class ApartmentServiceTest extends AbstractServiceTest{
#Autowired
ApartmentService service;
#Test
public void getList() {
List<Apartment> list = service.getList();
System.out.println(list.toString());
assertEquals(5, list.size());
}
}

Categories