Is there a way to include autoconfigurations based on profiles? (It would be nice if there was a spring.autonfigure.include)
I would like to connect to an h2 database for testing and for local development. For ease of development, I would like DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class, and DataSourceTransactionManagerAutoConfiguration.class autoconfigured. However, I would like to be able to easily switch to an oracle database which is defined in the application server and configured in a Configuration class. When switching to the oracle database, I need to exclude the autoconfiguration classes above:
// This only works for the oracle database - need to include autoconfig
// classes for h2 database
#SpringBootApplication(
exclude = {
DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class },
scanBasePackages = {
"foo.bar"
})
I have an "h2" profile that configures the h2 database and several other profiles in which I want the live database (local, dev, test, qual, prod). I could use the spring.autoconfigure.exclude property on each of the live database profiles, but sometimes I want to switch between "live" and h2 databases locally. I could also figure out exactly what the excluded autoconfigure classes are doing and manually configure in an "h2" profile but I'd rather not duplicate effort.
Anyone have ideas as to how to accomplish this?
I was able to get this to work by splitting up the #SpringBootApplication annotation and providing specific #EnableAutoConfiguration annotations.
#Configuration
#ComponentScan(basePackages = {"foo.bar"})
#EnableTransactionManagement
#EnableConfigurationProperties
public class App extends SpringBootServletInitializer {
public static void main(String... args) throws Exception {
SpringApplication.run(App.class, args);
}
}
For the h2 database, I enable the "h2" profile and use this class:
#Profile("h2")
#Configuration
#EnableAutoConfiguration
public class H2Config {
#Bean
public ServletRegistrationBean h2servletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(new WebServlet());
registration.addUrlMappings("/console/*");
return registration;
}
}
And for the "live" oracle database, I disable the "h2" profile and use this class:
#Profile("!h2")
#Configuration
#EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class })
public class NonH2Config {
}
Related
I have a Spring Boot + JPA application, configured for Oracle. But in my tests I am using H2 database.
When I create a base class for data-aware tests as
#ComponentScan("com.payeshgaran")
#DataJpaTest(properties = "spring.jpa.hibernate.ddl-auto=none")
#EnableTransactionManagement
#Sql(scripts = {"/sql/insurance-field.sql", "/sql/base-info.sql"})
#Transactional
public abstract class AbstractDataAwareTest {
}
and extend test classes from it, everything works fine, but if I put the configurations (specially #Sql on a #TestConfiguration class) and then use #ContextConfiguration class it doesn't work). Also if I configure the datasource by code in the #TestConfguration class as
#ComponentScan("com.payeshgaran")
#EnableTransactionManagement
#TestConfiguration
public class TestDataConfiguration {
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.H2)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.addScript("classpath:sql/insurance-field.sql")
.addScript("classpath:sql/base-info.sql")
.build();
}
}
again the initialization scripts won't run.
Anyone knows how should I use a single point of configuration without inheritance?
I have a Spring Boot web service backed by PostgreSQL 10. Since there were a few rounds of development Flyway is used to apply all necessary changes in DB.
Now I need to cover one of our modules with test and thus I need to mock PostgreSQL and I decided to use H2. The weird part is that when I run test I can see that I have no problems with DB migration, but when I try to use repository I get
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "MY_TABLE" not found; SQL statement:
I've tried to switch H2 to PostgreSQL mode with MODE=PostgreSQL and DATABASE_TO_LOWER=TRUE in connection url, but it didn't help. Also I keep connection open with DB_CLOSE_DELAY=-1. SQL queries in Flyway scripts seems fine, because on any attempt to modify them I can easily get syntax error.
Config properties
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:test;MODE=PostgreSQL;DATABASE_TO_UPPER=FALSE;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1
username: sa
password: sa
Test class
#SpringBootTest(classes = MyConfig.class)
#RunWith(SpringRunner.class)
public class MailServiceTest {
#Autowired
private MyRepo repo;
#Test
public void x() throws Exception {
repo.findAll(); // exception is thrown here
}
}
Config class
#SpringBootConfiguration
#EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
#PropertySource("classpath:custom-application.properties")
#ComponentScan(basePackages = {"com.example.myproj"})
#EnableJpaRepositories(basePackages = "com.example.myproj", entityManagerFactoryRef = "customEntityManagerFactory", transactionManagerRef = "customTransactionManager")
#EntityScan(basePackages = "com.example.myproj")
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableJpaAuditing(auditorAwareRef = "customAuditorAware")
#EnableScheduling
public class MyConfig {
...
}
Any clue how to fix this or at least where to look for solution? Thank you
Just stuck on the same issue this morning.
Solution was in a configuration inherited from AbstractJdbcConfiguration to change quote handling:
#Bean
#Override
public JdbcMappingContext jdbcMappingContext(Optional<NamingStrategy> namingStrategy, JdbcCustomConversions customConversions) {
JdbcMappingContext mappingContext = super.jdbcMappingContext(namingStrategy, customConversions);
mappingContext.setForceQuote(false);
return mappingContext;
}
TL;DR
Using JdbcTemplate to debug the test I found out, that select * from test is working, but select * from "test" doesn't.
It was the key finding to actually solve this issue.
In my case it was clear it is some kind of regression, because it worked before I upgraded Spring version.
So I started to search "quotes jdbc spring template" in google. Third in list was this:
https://spring.io/blog/2020/05/20/migrating-to-spring-data-jdbc-2-0
In a Spring Boot project that is not using JPA/Hibernate (but which cannot help but have these libraries on the classpath; long story, but completely unchangeable in this context), I need to stop whatever autoconfiguration is causing the error:
java.lang.IllegalArgumentException: At least one JPA metamodel must be present!
I am aware that I have to explicitly exclude JPA/Hibernate autoconfiguration. I do so in both the #SpringBootApplication-annotated Java class and also in the application.properties file.
#SpringBootApplication
#EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
#ComponentScan(basePackages = { "com" })
#EnableAsync
public class Application extends SpringBootServletInitializer {
...
}
and
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
Additionally, I've verified that these autoconfigurations are being excluded, by looking in the --debug output:
Exclusions:
-----------
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
Still, it fails when I don't have a JPA #Entity defined.
Why would this be?
We have a base JPA repository class with some additional utility methods that we use in our projects. Following the Spring Data JPA documentation we created the class and use the #EnableJpaRepositories annotation in a configuration class as in the following example:
#Configuration
#EnableJpaRepositories(basePackageClasses = MyApplication.class,
repositoryBaseClass = MyJpaRepositoryImpl.class)
public class SpringDataJpaMyRepositoryConfiguration {
}
We also set the basePackageClasses attribute so our repositories are found, as the configuration class is not in the application root package. Everything works as expected, so no problems so far.
Now we would like to create a spring boot starter to add the repository base class to our projects without further configuration, but we don't know how to do it. If we create an AutoConfiguration class with the EnableJpaRepositories annotation setting the repositoryBaseClass attribute, the automatic repository lookup strategy which looks for repositories under the class annotated with #SpringBootApplication doesn't work anymore.
And we can't use the basePackageClasses attribute as we don't know the main class or package of the project using the autoconfiguration.
Is there any way to do this? Maybe by redefining some bean in our autoconfiguration?
The ideal way would be something that allows to set the repository base class without having to define all the Spring Data JPA autoconfiguration again.
This question has driven me crazy at the time, so I thought I could help you on this.
Basically, the idea is to:
Create a configuration class for your Jpa config
Add #EntityScan and #EnableJpaRepositories referencing the same configuration class as the basePackageClass
Import this configuration class in your autoconfiguration
Create an annotation that you can then reuse where you need your Jpa config
In your example, you're using your Spring application class as your base for scannning.
I've put up a sample project to POC the main ideas at https://github.com/rdlopes/custom-jpa-demo
In the example, there's a project for the JPA entities/repositories exposing a JPA configuration:
#Configuration
#EntityScan(basePackageClasses = JpaConfiguration.class)
#EnableJpaRepositories(basePackageClasses = JpaConfiguration.class,
repositoryBaseClass = BaseRepositoryImpl.class)
public class JpaConfiguration {
}
Be careful with the common implementation for your repositories, you need to show a special signature:
#NoRepositoryBean
public class BaseRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID>
implements BaseRepository<T, ID> {
public BaseRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
}
#Override
public String someCustomMethod(ID id) {
return "Class for entity of id " + id + " is: " + getDomainClass().getSimpleName();
}
}
You can then create your auto configuration as such:
#Configuration
#ConditionalOnClass(CustomJpaRepositories.class)
#Import(JpaConfiguration.class)
public class JpaCustomAutoConfiguration {
}
Providing an annotation to keep things tidy and use it where you need JPA:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Inherited
public #interface CustomJpaRepositories {
}
Using your JPA classes will be as simple as having this annotation where you call your JPA repositories:
#SpringBootApplication
#CustomJpaRepositories
public class CustomJpaSampleApplication {
public static void main(String[] args) {
SpringApplication.run(CustomJpaSampleApplication.class, args);
}
#Bean
public CommandLineRunner dataInitializer(UserRepository userRepository) {
return args -> {
User user1 = new User();
user1.setName("user 1");
userRepository.save(user1);
User user2 = new User();
user2.setName("user 2");
userRepository.save(user2);
userRepository.findAll()
.forEach(user -> System.out.println(
userRepository.someCustomMethod(user.getId())));
};
}
}
Hope this helps you getting passed the head scratching moments :-)
EDIT: I've pretty much rewritten my answer - I misunderstood the original question
It's not the nicest solution but the only way I can see this working is by using SpEL inside #EnableJpaRepositories.
This can then go in your auto-configuration and use #ConditionalOnProperty to only auto-configure if the base package property is set
#Configuration
#ConditionalOnProperty("repositories-base-packages")
public class BaseRepositoryAutoConfiguration {
#Configuration
#EnableJpaRepositories(
repositoryBaseClass = MyJpaRepositoryImpl.class,
basePackages = "${repositories-base-packages}"
)
public static class JpaRepositoriesConfig { }
}
Then make sure you have a application.properties or application.yml which defines repositories-base-packages inside your application.
Not sure how you'd declare multiple base packages, my SpEL knowledge is primitive so not sure if it would even be possible.
I am new to Swagger and have implemented Swagger UI with spring mvc, I want to disable UI on production/live environment. I am struggling to figure it out.
This is what I am using
SwaggerConfig.java
#Configuration
#EnableSwagger2
public class SwaggerConfig {
}
RestController.java
#RestController
#Api(value="city", description="Operations pertaining to City Data")
#RequestMapping(value="/v1/city")
public class RestController {
#ApiOperation(value = "View city by stateId or stateName")
#RequestMapping(value="/search",method=RequestMethod.POST)
public ResponseEntity<Object> getCityBystateId(#RequestBody StateDto stateDto,Model model){
}
}
Look into the Spring's profile mechanism that lets you register different beans in different environments
When you bootstrap it according to doc, you can annotate your swagger config class on a class level with e.g. #Profile("dev"), thus enabling the swagger configuration for the environment of your choice
#Configuration
#EnableSwagger2
#Profile("dev")
public class SwaggerConfig {
}
Another way is to use a reverse proxy to reject access to Swagger Api on your production environment.
In this case your production installation is exactly the same as your development/test environment (then more compliant with DevOps method) and you can continue to access your Swagger API with internal calls.