I am trying to add Repository support to an Eclipselink JPA Spring project. Eclipselink requires LoadTimeWeaving - even though that's not absolutely true I'd later like to get Aspects working as well.
The production application runs under Tomcat, and I might be done by now if I had gone along and not tried to create JUnits. I have a feeling that maybe what I really have is an Eciplse (STS 3.4) problem since it seems to be ignoring my alternate class loader. But it seems so basic that this must work, and that I'm doing something wrong.
I am using exclusively Annotations and Java configuration. The relevant code below.
Configuration
#Configuration
#Profile("data")
#EnableJpaRepositories(basePackages="com.xxx.ieexb.repository",transactionManagerRef="transactionManager",entityManagerFactoryRef="entityManagerFactory")
#ComponentScan(basePackages= {"com.xxx.ieexb.jpa"
,"com.xxx.ieexb.repository"})
#EnableTransactionManagement
#EnableLoadTimeWeaving
#ImportResource("classpath:/properties-config.xml")
public class IeexbDataContextConfig {
#Value("#{ieexbProperties['com.xxx.ieexb.dataSource.Url']}")
public String dataSourceUrl;
#Value("#{ieexbProperties['com.xxx.ieexb.dataSource.Username']}")
public String dataSourceUsername;
#Value("#{ieexbProperties['com.xxx.ieexb.dataSource.Password']}")
public String dataSourcePassword;
#Value("#{ieexbProperties['com.xxx.ieexb.persistenceUnitName']}")
public String persistenceUnitName;
#Autowired
EntityManagerFactory entityManagerFactory;
#Bean()
public DriverManagerDataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.springframework.jdbc.datasource.DriverManagerDataSource");
dataSource.setUrl(dataSourceUrl);
dataSource.setUsername(dataSourceUsername);
dataSource.setPassword(dataSourcePassword);
return dataSource;
}
#Bean()
EclipseLinkJpaVendorAdapter eclipseLinkJpaVendorAdapter() {
EclipseLinkJpaVendorAdapter eclipseLinkJpaVendorAdapter = new EclipseLinkJpaVendorAdapter();
eclipseLinkJpaVendorAdapter.setShowSql(true);
eclipseLinkJpaVendorAdapter.setGenerateDdl(false);
eclipseLinkJpaVendorAdapter.setDatabasePlatform("org.eclipse.persistence.platform.database.OraclePlatform");
return eclipseLinkJpaVendorAdapter;
}
#Bean()
EclipseLinkJpaDialect jpaDialect() {
EclipseLinkJpaDialect jpaDialect = new EclipseLinkJpaDialect();
return jpaDialect;
}
#Bean()
public FactoryBean<EntityManagerFactory> entityManagerFactory() {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
//TODO Does this #Configuration do away with need for persistence.xml?
//emf.setPersistenceXmlLocation("classpath:persistence.xml");
emf.setPersistenceUnitName(persistenceUnitName);
emf.setDataSource(dataSource());
emf.setLoadTimeWeaver(loadTimeWeaver());
emf.setJpaDialect(new EclipseLinkJpaDialect());
emf.setJpaVendorAdapter(eclipseLinkJpaVendorAdapter());
//emf.setPersistenceProvider(persistenceProvider());
return emf;
}
#Bean()
public LoadTimeWeaver loadTimeWeaver() {
LoadTimeWeaver loadTimeWeaver = new ReflectiveLoadTimeWeaver();
return loadTimeWeaver;
}
}
The dead simple Entity (with some of the truly boring columns removed):
#Entity
#Cacheable(false)
#Table(name="PENDING_QUEUE"
, uniqueConstraints = #UniqueConstraint(columnNames="IEEXB_ID,QUEUE_TIMESTAMP")
)
public class PendingQueue implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
#AttributeOverrides( {
#AttributeOverride(name="ieexbId", column=#Column(name="IEEXB_ID", nullable=false) ),
#AttributeOverride(name="queueTimestamp", column=#Column(name="QUEUE_TIMESTAMP", nullable=false) ) } )
private PendingQueueId id;
#Column(name="IE_USER", nullable=false, length=21)
private String ieUser;
#Column(name="COMMAND_TYPE", nullable=false, length=3)
private String commandType;
public PendingQueue() {
;
}
public PendingQueue(PendingQueueId id, String ieUser, String commandType) {
super();
this.id = id;
this.ieUser = ieUser;
this.commandType = commandType;
}
public PendingQueueId getId() {
return id;
}
public void setId(PendingQueueId id) {
this.id = id;
}
public String getIeUser() {
return ieUser;
}
public void setIeUser(String ieUser) {
this.ieUser = ieUser;
}
public String getCommandType() {
return commandType;
}
public void setCommandType(String commandType) {
this.commandType = commandType;
}
....
}
The Repository of course:
#Repository
public interface PendingQueueRepository extends CrudRepository<PendingQueue, PendingQueueId> {
List<PendingQueue> findByIeUser(String ieUser);
}
And finally the JUnit:
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles(profiles = {"data"})
#ContextConfiguration(classes = IeexbDataContextConfig.class, loader = AnnotationConfigContextLoader.class)
public class TestPendingQueue {
#Autowired
PendingQueueRepository pendingQueueRepository;
#Test
public void testFindByIeUser() {
List<PendingQueue> results = pendingQueueRepository.findByIeUser("JPA.XXX.BOB1");
System.out.println("Found Items:" + results.size());
System.out.println("First item is:" + results.get(0));
}
}
I am trying to run the JUnit with this VM ARG:
-javaagent:C:\Users\Terry\.m2\repository\org\springframework\spring-agent\2.5.6.SEC03\spring-agent-2.5.6.SEC03.jar
I also tried adding the AspectJ weaver, but it didn't help
-javaagent:C:\Users\Terry\.m2\repository\org\aspectj\aspectjweaver\1.8.0\aspectjweaver-1.8.0.jar
I have not tried the Spring Tomcat weaver but that doesn't seem like the right direction. I've read people then had problems with JUnit finding class files.
Without pasting in the rather huge stack trace it all boils down to this:
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.instrument.classloading.LoadTimeWeaver
com.xxx.ieexb.config.IeexbDataContextConfig.loadTimeWeaver()] threw exception; nested exception is java.lang.IllegalStateException: ClassLoader [sun.misc.Launcher$AppClassLoader] does NOT provide an 'addTransformer(ClassFileTransformer)' method.
Which is of course true. That classloader doesn't support weaving. But I have tried very very hard to not use that loader. Any help appreciated
I have a similar setup and adding the following VM args for unit tests works for me:
-javaagent:/path/to/spring-instrument-4.0.0.RELEASE.jar -javaagent:/path/to/aspectjweaver-1.8.0.jar
It appears the problem was that in the #Configuration class I included a definition for a LoadTimeWeaver bean. I also set that value inside the EntityManagerFactory constructor. By removing that I no longer get the runtime error during spring container startup.
Looking at the Spring console it says my Entity Beans were transformed (woven) so perhaps Lazy loading would still work - I don't have any DB associations to try it on in this particular project.
One other note is not only is persistence.xml NOT required it is not allowed. Eclipselink was hunting down another copy of persistence.xml I didn't know about and that was causing the Entity backing my Repository to not get discovered.
But right now I believe the example I've posted (minus the loadTimeWeaving bean) is, remarkably, one of the best, or at least most complete, examples out there - who'd have expected that!
i couldn't get the above to work. amazon aws doesn't let me put the instrumentation jar in tomcat/lib folder so i could not use -javaagent approach (believe me i tried!). i had to switch to eclipselink static weaving
it's not too hard, a persistence.xml file is required but if you're already using annotated configuration it can be as short as:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="ADD_NAME_HERE">
<!--this is necessary for the eclipselink static weaving maven plugin-->
<!--this setting auto-detects entities in the project-->
<exclude-unlisted-classes>false</exclude-unlisted-classes>
</persistence-unit>
</persistence>
but note that it MUST be in your project that contains the entity classes. i made the mistake of dropping the persistence.xml file and the maven plugin in the pom.xml file into my website project not my domain project.
If one needs to set -javaagent by maven, it can do it like this:
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20</version>
<configuration>
<argLine>
-javaagent:"${settings.localRepository}/org/springframework/spring-instrument/${org.springframework.version}/spring-instrument-${org.springframework.version}.jar"
</argLine>
</configuration>
</plugin>
</plugins>
(just set property for ${org.springframework.version} or replace for corresponding version)
This way it's associated with your project, and not with your jvm or webserver
Related
I am using Drools with Spring Boot 2.3 and I have implemented the persistent aware KieSession, in which MySQL is used for storing the session. I have successfully integrated the default EntityManagerFactory of Spring Boot with Drools but my problem is with transactions. By default, Drools uses Optimistic Lock during transactions but it allows us to use the Pessimistic Lock as well, which is what I want. Now while firing rules, Drools persists/updates the KieSession in MySQL with the following query:
update SessionInfo set lastModificationDate=?, rulesByteArray=?, startDate=?, OPTLOCK=? where id=? and OPTLOCK=?
Now the above statement is executed twice if I do not use transactions using the #Transactional annotation in the method, and if #Transactional is used then the above statement is executed only once after firing the rules.
Now, if I manually change the value of the OPTLOCK field then Drools throws an exception:
javax.persistence.OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [org.drools.persistence.info.SessionInfo#1]
followed by:
Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [org.drools.persistence.info.SessionInfo#1]
I am unable to post the entire Stacktrace due to text length limitations here. The entire stacktrace can be viewed in this GitHub project.
I am not sure whether Drools is using the Pessimistic Lock as defined in the environment. About my session implementation, I want to have a single KieSession since I am using KieSession as a Bean.
Below is my implementation:
The configuration class:
#Configuration
public class DynamicDroolsConfig {
private KieServices kieServices;
private KieFileSystem kieFileSystem;
#Autowired
private PersistentSessionDAO persistentSessionDAO;
#PersistenceUnit
private EntityManagerFactory entityManagerFactory;
#Autowired
private PlatformTransactionManager platformTransactionManager;
#PostConstruct
private void init() {
this.kieServices = KieServices.Factory.get();
this.kieFileSystem = kieServices.newKieFileSystem();
}
#Bean
public KieServices getKieServices() {
return this.kieServices;
}
#Bean
public KieContainer getKieContainer() {
kieFileSystem.write(ResourceFactory.newClassPathResource("rules/rules.drl"));
final KieRepository kieRepository = kieServices.getRepository();
kieRepository.addKieModule(kieRepository::getDefaultReleaseId);
KieBuilder kb = kieServices.newKieBuilder(kieFileSystem).buildAll();
KieModule kieModule = kb.getKieModule();
return kieServices.newKieContainer(kieModule.getReleaseId());
}
#Bean
public KieFileSystem getFileSystem() {
return kieFileSystem;
}
#Bean
public KieSession kieSession() {
List<SessionInfo> sessionDetails = persistentSessionDAO.getSessionDetails();
if (sessionDetails.size() == 0) {
return kieServices.getStoreServices().newKieSession(getKieContainer().getKieBase(), null, getEnv());
} else {
return kieServices.getStoreServices().loadKieSession(sessionDetails.get(0).getId(), getKieContainer().getKieBase(), null, getEnv());
}
}
private Environment getEnv() {
Environment env = kieServices.newEnvironment();
env.set(EnvironmentName.ENTITY_MANAGER_FACTORY, entityManagerFactory);
env.set(EnvironmentName.TRANSACTION_MANAGER, platformTransactionManager);
env.set(EnvironmentName.USE_PESSIMISTIC_LOCKING, true);
env.set(EnvironmentName.USE_PESSIMISTIC_LOCKING_MODE, LockModeType.PESSIMISTIC_FORCE_INCREMENT.name());
return env;
}
}
The controller class:
#RestController
public class MyController {
#Autowired
private KieSession kieSession;
#Transactional
#GetMapping("fire-person")
public void firePerson() {
Person person = new Person();
person.setName("Christy");
kieSession.insert(person);
kieSession.fireAllRules();
}
}
The Fact class
public class Person implements Serializable {
private String name;
private int age;
private String gender;
private String toCompareName;
private String toCompareGender;
// getters and setters
}
The repository interface:
public interface DroolsSessionRepository extends JpaRepository<SessionInfo, Long> {
}
The service class:
#Service
public class PersistentSessionDAO {
#Autowired
private DroolsSessionRepository droolsSessionRepository;
public List<SessionInfo> getSessionDetails() {
return droolsSessionRepository.findAll();
}
}
The runner class:
#EntityScan(basePackages = {"com.sam.springdroolspersistence.entity", "org.drools.persistence.info"})
#EnableJpaRepositories
#SpringBootApplication
public class SpringDroolsPersistenceApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDroolsPersistenceApplication.class, args);
}
}
The Drools dependencies used:
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-persistence-jpa</artifactId>
<version>${drools-version}</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>${drools-version}</version>
</dependency>
<dependency>
<groupId>org.jbpm</groupId>
<artifactId>jbpm-persistence-jpa</artifactId>
<version>${drools-version}</version>
</dependency>
The code implementation can also be found in this GitHub Project. Any kind of help/suggestions will be much appreciated. Thank you.
Pessimistic locking is implemented only in JBPM see here
There's no such functionality in Drools persistence, SessionInfo will always use OptimisticLocking based on JPA's #Version annotation.
If you need such feature, please file a feature request on Drools' Jira
Spring Boot allows us to replace our application.properties files with YAML equivalents. However, I seem to hit a snag with my tests. If I annotate my TestConfiguration (a simple Java config), it is expecting a properties file.
For example this doesn't work:
#PropertySource(value = "classpath:application-test.yml")
If I have this in my YAML file:
db:
url: jdbc:oracle:thin:#pathToMyDb
username: someUser
password: fakePassword
And I'd be leveraging those values with something like this:
#Value("${db.username}") String username
However, I end up with an error like so:
Could not resolve placeholder 'db.username' in string value "${db.username}"
How can I leverage the YAML goodness in my tests as well?
Spring-boot has a helper for this, just add
#ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
at the top of your test classes or an abstract test superclass.
Edit: I wrote this answer five years ago. It doesn't work with recent versions of Spring Boot. This is what I do now (please translate the Kotlin to Java if necessary):
#TestPropertySource(locations=["classpath:application.yml"])
#ContextConfiguration(
initializers=[ConfigFileApplicationContextInitializer::class]
)
is added to the top, then
#Configuration
open class TestConfig {
#Bean
open fun propertiesResolver(): PropertySourcesPlaceholderConfigurer {
return PropertySourcesPlaceholderConfigurer()
}
}
to the context.
As it was mentioned #PropertySource doesn't load yaml file. As a workaround load the file on your own and add loaded properties to Environment.
Implemement ApplicationContextInitializer:
public class YamlFileApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
try {
Resource resource = applicationContext.getResource("classpath:file.yml");
YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
PropertySource<?> yamlTestProperties = sourceLoader.load("yamlTestProperties", resource, null);
applicationContext.getEnvironment().getPropertySources().addFirst(yamlTestProperties);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Add your initializer to your test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class, initializers = YamlFileApplicationContextInitializer.class)
public class SimpleTest {
#Test
public test(){
// test your properties
}
}
#PropertySource can be configured by factory argument. So you can do something like:
#PropertySource(value = "classpath:application-test.yml", factory = YamlPropertyLoaderFactory.class)
Where YamlPropertyLoaderFactory is your custom property loader:
public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
if (resource == null){
return super.createPropertySource(name, resource);
}
return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null);
}
}
Inspired by https://stackoverflow.com/a/45882447/4527110
Another option is to set the spring.config.location through #TestPropertySource:
#TestPropertySource(properties = { "spring.config.location = classpath:<path-to-your-yml-file>" }
#PropertySource only supports properties files (it's a limitation from Spring, not Boot itself). Feel free to open a feature request ticket in JIRA.
From Spring Boot 1.4, you can use the new #SpringBootTest annotation to achieve this more easily (and to simplify your integration test setup in general) by bootstrapping your integration tests using Spring Boot support.
Details on the Spring Blog.
As far as I can tell, this means you get all the benefits of Spring Boot's externalized config goodness just like in your production code, including automatically picking up YAML config from the classpath.
By default, this annotation will
... first attempt to load #Configuration from any inner-classes, and if that fails, it will search for your primary #SpringBootApplication class.
but you can specify other configuration classes if required.
For this particular case, you can combine #SpringBootTest with #ActiveProfiles( "test" ) and Spring will pick up your YAML config, provided it follows the normal Boot naming standards (i.e. application-test.yml).
#RunWith( SpringRunner.class )
#SpringBootTest
#ActiveProfiles( "test" )
public class SpringBootITest {
#Value("${db.username}")
private String username;
#Autowired
private MyBean myBean;
...
}
Note: SpringRunner.class is the new name for SpringJUnit4ClassRunner.class
The approach to loading the yaml properties, IMHO can be done in two ways:
a. You can put the configuration in a standard location - application.yml in the classpath root - typically src/main/resources and this yaml property should automatically get loaded by Spring boot with the flattened path name that you have mentioned.
b. The second approach is a little more extensive, basically define a class to hold your properties this way:
#ConfigurationProperties(path="classpath:/appprops.yml", name="db")
public class DbProperties {
private String url;
private String username;
private String password;
...
}
So essentially this is saying that load the yaml file and populate the DbProperties class based on the root element of "db".
Now to use it in any class you will have to do this:
#EnableConfigurationProperties(DbProperties.class)
public class PropertiesUsingService {
#Autowired private DbProperties dbProperties;
}
Either of these approaches should work for you cleanly using Spring-boot.
Since Spring Boot 2.4.0 you can use ConfigDataApplicationContextInitializer as follows:
#SpringJUnitConfig(
classes = { UserAccountPropertiesTest.TestConfig.class },
initializers = { ConfigDataApplicationContextInitializer.class }
)
class UserAccountPropertiesTest {
#Configuration
#EnableConfigurationProperties(UserAccountProperties.class)
static class TestConfig { }
#Autowired
UserAccountProperties userAccountProperties;
#Test
void getAccessTokenExpireIn() {
assertThat(userAccountProperties.getAccessTokenExpireIn()).isEqualTo(120);
}
#Test
void getRefreshTokenExpireIn() {
assertThat(userAccountProperties.getRefreshTokenExpireIn()).isEqualTo(604800);
}
}
See also: https://www.baeldung.com/spring-boot-testing-configurationproperties#YAML-binding
I found a workaround by using #ActiveProfiles("test") and adding an application-test.yml file to src/test/resources.
It ended up looking like this:
#SpringApplicationConfiguration(classes = Application.class, initializers = ConfigFileApplicationContextInitializer.class)
#ActiveProfiles("test")
public abstract class AbstractIntegrationTest extends AbstractTransactionalJUnit4SpringContextTests {
}
The file application-test.yml just contains the properties that I want to override from application.yml (which can be found in src/main/resources).
I have tried all of the listed questions, but all of them not work for my task: using specific yaml file for some unit test.
In my case, it works like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(initializers = {ConfigFileApplicationContextInitializer.class})
#TestPropertySource(properties = {"spring.config.location=file:../path/to/specific/config/application.yml"})
public class SomeTest {
#Value("${my.property.value:#{null}}")
private String value;
#Test
public void test() {
System.out.println("value = " + value);
}
}
it's because you have not configure snakeyml.
spring boot come with #EnableAutoConfiguration feature.
there is snakeyml config too when u call this annotation..
this is my way:
#Configuration
#EnableAutoConfiguration
public class AppContextTest {
}
here is my test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(
classes = {
AppContextTest.class,
JaxbConfiguration.class,
}
)
public class JaxbTest {
//tests are ommited
}
I needed to read some properties into my code and this works with spring-boot 1.3.0.RELEASE
#Autowired
private ConfigurableListableBeanFactory beanFactory;
// access a properties.yml file like properties
#Bean
public PropertySource properties() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource("properties.yml"));
propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
// properties need to be processed by beanfactory to be accessible after
propertySourcesPlaceholderConfigurer.postProcessBeanFactory(beanFactory);
return propertySourcesPlaceholderConfigurer.getAppliedPropertySources().get(PropertySourcesPlaceholderConfigurer.LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME);
}
Loading custom yml file with multiple profile config in Spring Boot.
1) Add the property bean with SpringBootApplication start up as follows
#SpringBootApplication
#ComponentScan({"com.example.as.*"})
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Bean
#Profile("dev")
public PropertySourcesPlaceholderConfigurer propertiesStage() {
return properties("dev");
}
#Bean
#Profile("stage")
public PropertySourcesPlaceholderConfigurer propertiesDev() {
return properties("stage");
}
#Bean
#Profile("default")
public PropertySourcesPlaceholderConfigurer propertiesDefault() {
return properties("default");
}
/**
* Update custom specific yml file with profile configuration.
* #param profile
* #return
*/
public static PropertySourcesPlaceholderConfigurer properties(String profile) {
PropertySourcesPlaceholderConfigurer propertyConfig = null;
YamlPropertiesFactoryBean yaml = null;
propertyConfig = new PropertySourcesPlaceholderConfigurer();
yaml = new YamlPropertiesFactoryBean();
yaml.setDocumentMatchers(new SpringProfileDocumentMatcher(profile));// load profile filter.
yaml.setResources(new ClassPathResource("env_config/test-service-config.yml"));
propertyConfig.setProperties(yaml.getObject());
return propertyConfig;
}
}
2) Config the Java pojo object as follows
#Component
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(Include.NON_NULL)
#ConfigurationProperties(prefix = "test-service")
public class TestConfig {
#JsonProperty("id")
private String id;
#JsonProperty("name")
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3) Create the custom yml (and place it under resource path as follows,
YML File name : test-service-config.yml
Eg Config in the yml file.
test-service:
id: default_id
name: Default application config
---
spring:
profiles: dev
test-service:
id: dev_id
name: dev application config
---
spring:
profiles: stage
test-service:
id: stage_id
name: stage application config
<dependency>
<groupId>com.github.yingzhuo</groupId>
<artifactId>spring-boot-stater-env</artifactId>
<version>0.0.3</version>
</dependency>
Welcome to use my library. Now yaml, toml, hocon is supported.
Source: github.com
This is not an answer to the original question, but an alternative solution for a need to have a different configuration in a test...
Instead of #PropertySource you can use -Dspring.config.additional-location=classpath:application-tests.yml.
Be aware, that suffix tests does not mean profile...
In that one YAML file one can specify multiple profiles, that can kind of inherit from each other, read more here - Property resolving for multiple Spring profiles (yaml configuration)
Then, you can specify in your test, that active profiles (using #ActiveProfiles("profile1,profile2")) are profile1,profile2 where profile2 will simply override (some, one does not need to override all) properties from profile1.
project demo url: https://github.com/Forest10/spring-boot-family/tree/spring-boot-with-yml
I run this answer in my prod env!!! so if you against this ans. please test first!!!
There is no need to add like YamlPropertyLoaderFactory or YamlFileApplicationContextInitializer. You should convert your idea
Follow these steps:
Just add applicationContext.xml like
#ImportResource({"classpath:applicationContext.xml"})
to your ApplicationMainClass.
and your applicationContext.xml should write like this
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-autowire="byName"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:property-placeholder location="classpath*:*.yml"/>
</beans>
This can help scan your application-test.yml
db:
url: jdbc:oracle:thin:#pathToMyDb
username: someUser
password: fakePassword
Enhancing Mateusz Balbus answer.
Modified YamlFileApplicationContextInitializer class where YAML location is defined per test class. It does not work per test, unfortunately.
public abstract class YamlFileApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
/***
* Return location of a YAML file, e.g.: classpath:file.yml
*
* #return YAML file location
*/
protected abstract String getResourceLocation();
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
try {
Resource resource = applicationContext.getResource(getResourceLocation());
YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
PropertySource<?> yamlTestProperties = sourceLoader.load("yamlTestProperties", resource, null);
applicationContext.getEnvironment().getPropertySources().addFirst(yamlTestProperties);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Usage:
Create subclass of YamlFileApplicationContextInitializer with defined getResourceLocation() method and add this subclass into #SpringApplicationConfiguration annotation.
This way it is easiest to make the test class itself.
#RunWith(SpringRunner.class)
#SpringApplicationConfiguration(classes = Application.class, initializers = SimpleTest.class)
public class SimpleTest extends YamlFileApplicationContextInitializer {
#Override
protected String getResourceLocation() {
return "classpath:test_specific.yml";
}
#Test
public test(){
// test your properties
}
}
Here's an improved version of YamlPropertyLoaderFactory which supports the new PropertySource.ignoreResourceNotFound, based on this answer:
Java:
public final class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
private final YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader();
#NotNull
public PropertySource createPropertySource(
#Nullable String name,
#NotNull EncodedResource resource
) {
try {
String parsedName;
if (name != null && !name.equals(""))
parsedName = name;
else parsedName = resource.getResource().getFilename();
return yamlPropertySourceLoader.load(parsedName, resource.getResource()).get(0);
} catch (Exception e) {
Exception possibleFileNotFoundException = ExceptionUtils.throwableOfType(e, FileNotFoundException.class);
throw possibleFileNotFoundException != null ? possibleFileNotFoundException : e;
}
}
}
// Usage
#PropertySource(
value = {"file:./my-optional-config.yml"},
factory = YamlPropertyLoaderFactory.class,
ignoreResourceNotFound = true
)
Kotlin:
class YamlPropertyLoaderFactory : DefaultPropertySourceFactory() {
private val yamlPropertySourceLoader = YamlPropertySourceLoader()
override fun createPropertySource(
name: String?,
resource: EncodedResource
): PropertySource<*> = try {
(
yamlPropertySourceLoader.load(
if (name != null && name.isNotBlank()) name else resource.resource.filename,
resource.resource
)
)[0]
} catch (e: Exception) {
throw ExceptionUtils.throwableOfType(e, FileNotFoundException::class.java) ?: e
}
}
// Usage
#PropertySource(
value = ["file:/my-optional-config.yml"],
factory = YamlPropertyLoaderFactory::class,
ignoreResourceNotFound = true
)
I have a Spring 3.1 #Configuration that needs a property foo to build a bean. The property is defined in defaults.properties but may be overridden by the property in overrides.properties if the application has an active override Spring profile.
Without the override, the code would look like this, and work...
#Configuration
#PropertySource("classpath:defaults.properties")
public class MyConfiguration {
#Autowired
private Environment environment;
#Bean
public Bean bean() {
...
// this.environment.getRequiredProperty("foo");
...
}
}
I would like a #PropertySource for classpath:overrides.properties contingent on #Profile("overrides"). Does anyone have any ideas on how this could be achieved? Some options I've considered are a duplicate #Configuration, but that would violate DRY, or programmatic manipulation of the ConfigurableEnvironment, but I'm not sure where the environment.getPropertySources.addFirst() call would go.
Placing the following in an XML configuration works if I inject the property directly with #Value, but not when I use Environment and the getRequiredProperty() method.
<context:property-placeholder ignore-unresolvable="true" location="classpath:defaults.properties"/>
<beans profile="overrides">
<context:property-placeholder ignore-unresolvable="true" order="0"
location="classpath:overrides.properties"/>
</beans>
Update
If you're trying to do this now, check out Spring Boot's YAML support, particularly the 'Using YAML instead of Properties' section. The profile support there would make this question moot, but there isn't #PropertySource support yet.
Add the overriding #PropertySource in a static inner class. Unfortunately, you must specify all property sources together which means creating a "default" profile as the alternative to "override".
#Configuration
public class MyConfiguration
{
#Configuration
#Profile("default")
#PropertySource("classpath:defaults.properties")
static class Defaults
{ }
#Configuration
#Profile("override")
#PropertySource({"classpath:defaults.properties", "classpath:overrides.properties"})
static class Overrides
{
// nothing needed here if you are only overriding property values
}
#Autowired
private Environment environment;
#Bean
public Bean bean() {
...
// this.environment.getRequiredProperty("foo");
...
}
}
I suggest, defining two files, where the second is optional with the profile as suffix:
#Configuration
#PropertySources({
#PropertySource("classpath:/myconfig.properties"),
#PropertySource(value = "classpath:/myconfig-${spring.profiles.active}.properties", ignoreResourceNotFound = true)
})
public class MyConfigurationFile {
#Value("${my.prop1}")
private String prop1;
#Value("${my.prop2}")
private String prop2;
}
You can do:
<context:property-placeholder location="classpath:${spring.profiles.active}.properties" />
Edit: if you need something more advanced, you can register your PropertySources on application startup.
web.xml
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>com.xxx.core.spring.properties.PropertySourcesApplicationContextInitializer</param-value>
</context-param>
file you create:
public class PropertySourcesApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final Logger LOGGER = LoggerFactory.getLogger(PropertySourcesApplicationContextInitializer.class);
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
LOGGER.info("Adding some additional property sources");
String[] profiles = applicationContext.getEnvironment().getActiveProfiles()
// ... Add property sources according to selected spring profile
// (note there already are some property sources registered, system properties etc)
applicationContext.getEnvironment().getPropertySources().addLast(myPropertySource);
}
}
Once you've done it you just need to add in your context:
<context:property-placeholder/>
I can't really answer to your question about multiple profiles but I guess you activate them on such an initializer, and you could register the appropriate PropertySource items during profile activations.
I can't think of any other way than one you have suggested Emerson, which is to define this bean in a separate #Configuration file with an #Profile annotation:
#Configuration
#Profile("override")
#PropertySource("classpath:override.properties")
public class OverriddenConfig {
#Autowired
private Environment environment;
#Bean
public Bean bean() {
//if..
}
}
In case you need to support multiple profiles you could do something like this:
#Configuration
public class Config {
#Configuration
#Profile("default")
#PropertySource("classpath:application.properties")
static class DefaultProperties {
}
#Configuration
#Profile("!default")
#PropertySource({"classpath:application.properties", "classpath:application-${spring.profiles.active}.properties"})
static class NonDefaultProperties {
}
}
That way you don't need to define a static configuration class for each profile.
Thanks David Harkness for putting me into the right direction.
Note: This answer provides an alternate solution to using properties files with #PropertySource. I went this route because it was too cumbersome trying to work with multiple properties files that may each have overrides while avoiding repetitive code.
Create a POJO interface for each related set of properties to define their names and types.
public interface DataSourceProperties
{
String driverClassName();
String url();
String user();
String password();
}
Implement to return the default values.
public class DefaultDataSourceProperties implements DataSourceProperties
{
public String driverClassName() { return "com.mysql.jdbc.Driver"; }
...
}
Subclass for each profile (e.g. development, production) and override any values that differ from the default. This requires a set of mutually-exclusive profiles, but you can easily add "default" as the alternative to "overrides".
#Profile("production")
#Configuration
public class ProductionDataSourceProperties extends DefaultDataSourceProperties
{
// nothing to override as defaults are for production
}
#Profile("development")
#Configuration
public class DevelopmentDataSourceProperties extends DefaultDataSourceProperties
{
public String user() { return "dev"; }
public String password() { return "dev"; }
}
Finally, autowire the properties configurations into the other configurations that need them. The advantage here is that you don't repeat any #Bean creation code.
#Configuration
public class DataSourceConfig
{
#Autowired
private DataSourceProperties properties;
#Bean
public DataSource dataSource() {
BoneCPDataSource source = new BoneCPDataSource();
source.setJdbcUrl(properties.url());
...
return source;
}
}
I am still not convinced I'll stick with this over manually configuring properties files based on the active profiles in a servlet context initializer. My thought was that doing manual configuration would not be as amenable to unit testing, but I'm not so sure now. I really prefer reading properties files to a list of property accessors.
All mentioned here solutions are a bit awkward, work only with one profile preset, and they won't work with more/other profiles. Currently a Spring team refuses to introduce this feature. But here's the working workaround I've found:
package com.example;
public class MyPropertySourceFactory implements PropertySourceFactory, SpringApplicationRunListener {
public static final Logger logger = LoggerFactory.getLogger(MyPropertySourceFactory.class);
#NonNull private static String[] activeProfiles = new String[0];
// this constructor is used for PropertySourceFactory
public MyPropertySourceFactory() {
}
// this constructor is used for SpringApplicationRunListener
public MyPropertySourceFactory(SpringApplication app, String[] params) {
}
#Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
activeProfiles = environment.getActiveProfiles();
}
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) throws IOException {
logger.info("Loading: {} with profiles: {}", encodedResource.toString(), activeProfiles);
// here you know all profiles and have the source Resource with main
// properties, just try to load other resoures in the same path with different
// profile names and return them as a CompositePropertySource
}
}
To make it working you have to have src/main/resources/META-INF/spring.factories with the following content:
org.springframework.boot.SpringApplicationRunListener=com.example.MyPropertySourceFactory
Now you can put your custom properties file somewhere and load it with #PropertySources:
#Configuration
#PropertySource(value = "classpath:lib.yml", factory = MyPropertySourceFactory.class)
public class PropertyLoader {
}
I am making a web application on STS. I am using jars of Spring 3.1.0 and HIbernate 4.0.1. I am including jars in project build path.
In DAO layer when I am trying to make a HibernateTemplate object , it is not getting instantiate, It is null there. I don't understand why it is null.
Earlier I was getting an error like NoClassDefinitionFound: org.springframework.orm.hibernate3.HibernateTemplate....Then I included these jars in WEB-INF->lib folder and then this error was removed but still hibernateTemplate object is null. Can there be any issue regarding position of beans.xml in project folders. ? Can anyone help me.
Below is code for my beans.xml and Userinfo.java.
[b]Beans.xml[/b]
Only relevant part of bean.xml
<bean id="hibTemplateBean" class="org.springframework.orm.hibernate3.HibernateTemplate" >
<property name="sessionFactory" ref="sfBean" />
</bean>
[b]UserinfoDao.java[/b]
package com.home.dao;
import org.springframework.orm.hibernate3.HibernateTemplate;
import com.home.pojo.User;
public class UserinfoDao {
public UserinfoDao() {
super();
}
private static HibernateTemplate hibernateTemplate;
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
UserinfoDao.hibernateTemplate = hibernateTemplate;
}
public static void fetchUserInfo(){
try{
User user = (User)hibernateTemplate.get(User.class, 111);
}catch(NullPointerException npe){
npe.printStackTrace();
}
}
}
You are using Hibernate 4 and you are using org.springframework.orm.hibernate3.HibernateTemplate from Hibernate 3 package.
This template is not supported anymore. You can do declarative transaction management.
Your version makes me believe that you are simply accessing your bean with static method:
UserinfoDao.fetchUserInfo();
Looks like you are missing few key points of Spring. First of all you should not use static methods and fields, try with the following class:
public class UserinfoDao {
private HibernateTemplate hibernateTemplate;
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
public void fetchUserInfo(){
try{
User user = (User)hibernateTemplate.get(User.class, 111);
}catch(NullPointerException npe){
npe.printStackTrace();
}
}
}
Now you need to obtain an instance of Spring bean somehow. You cannot simply use new UserinfoDao() operator. You must either declare your bean in XML or via annotations (and declare dependency on HibernateTemplate.
Finally you shouldn't normally catch NullPointerException, but I understand this if for ddebugging purposes.
I have been trying to create a Spring project that uses MyBatis for the data access layer as a proof of concept for my team. I really want to avoid XML configuration if at all possible, so I'm attempting to wire everything together using annotated #Configuration classes.
Everything seems to be wired correctly, but my mapper beans are not being AutoWired into my service layer.
In my example I'm trying to wire together a UserDao, User entity, and a UserService.
UserDao
public interface UserDao {
#Select("SELECT * FROM users WHERE id = #{userId}")
User get(#Param("userId") Integer userId);
}
User
#Component("User")
public class User implements Entity {
public Integer userId;
public String username;
/** ... getters/setters ommitted **/
}
UserServiceImpl
#Service("UserService")
public class UserServiceImpl {
private UserDao userDao = null;
public User getUserDetails(Integer userId) {
return userDao.get(userId);
}
#Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
I'm wiring these together using two configuration classes.
ApplicationContextConfig
#Configuration
#EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.ENABLED)
#Import(DefaultDataAccessConfig.class) // I'm importing this because I thought ordering might be important, otherwise I was hoping to just let the component scanning pull in additional configuration files
#ComponentScan(basePackages="com.example.gwtspringpoc.server",
excludeFilters=#Filter(type=FilterType.ANNOTATION,
value=Controller.class))
public class ApplicationContextConfig {
/** No bean definitions needed here **/
}
DefaultDataAccessConfig
#Configuration
#EnableTransactionManagement
public class DefaultDataAccessConfig implements TransactionManagementConfigurer {
#Bean
public DataSource dataSource() {
OracleDataSource ods = null;
try {
ods = new OracleDataSource();
} catch (SQLException e) {
throw new RuntimeException(e);
}
ods.setURL("jdbc:oracle:thin:#//localhost:9601/sid");
ods.setUser("user");
ods.setPassword("pass");
return ods;
}
#Override
#Bean(name="transactionManager")
public PlatformTransactionManager annotationDrivenTransactionManager() {
return new DataSourceTransactionManager(dataSource());
}
#Bean
public SqlSessionFactory sqlSessionFactory() {
SqlSessionFactoryBean sf = new SqlSessionFactoryBean();
sf.setDataSource(dataSource());
try {
return (SqlSessionFactory) sf.getObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
#Bean
public SqlSession sqlSessionTemplate() {
return new SqlSessionTemplate(sqlSessionFactory());
}
/*
* This did not work at all. It seems to be configured correctly, but the UserDao bean never
* got created at any stage, which was very disappointing as I was hoping not to have to
* create a bean definition for each DAO manually
*/
/*#Bean
public static MapperScannerConfigurer mapperScannerConfig() {
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.ca.spna.gwtspringpoc.server.model.dao");
msc.setAnnotationClass(Repository.class);
return msc;
}*/
/*
* Because the above code did not work, I decided to create the mapping manually.
* This is most likely my problem - something about this setup. My understanding
* is that the MapperFactoryBean once instantiated by Spring, will create a proxy
* object of type UserDao with the name "userDao" that can be injected elsewhere.
*/
#Bean
public MapperFactoryBean<UserDao> userDao() {
MapperFactoryBean<UserDao> mfb = new MapperFactoryBean<UserDao>();
mfb.setMapperInterface(UserDao.class);
return mfb;
}
}
You can read the comments above the last two methods in the above code snippet to gain more insight into how I'm creating the UserDao bean.
Once I got all the configuration setup, I created a unit test to try to test the UserService using the AnnotationConfigContextLoader, but was immediately hit with the following exception when trying to run the test:
Exception
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void com.example.gwtspringpoc.server.service.UserServiceImpl.setUserDao(com.example.gwtspringpoc.server.model.dao.UserDao); nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.example.gwtspringpoc.server.model.dao.UserDao] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
After seeing that, I commented out the #Autowired in the UserService and went back to my unit test and injected the ApplicationContext so I could inspect it, and the bean named "userDao" is in fact a MapperProxy instance.
So, is my understanding of how the MapperFactoryBean works off track or is it just not very compatible with annotation driven configuration? Additionally, if anyone has any idea how to make the MapperScannerConfigurer work correctly, I would appreciate it greatly!
After some time I was able to figure things out, so I'll answer my own question in case others run into something similar as there wasn't a whole lot of information available out there and it took some searching.
The problem comes down to the fact that MapperScannerConfigurer is a BeanDefinitionRegistryPostProcessor. As it turns out, this is the same mechanism used to process the #Configuration files and register the #Bean annotated methods. Unfortunately, one BeanDefinitionRegistryPostProcessor cannot make use of another, according to this Spring Jira ticket: https://jira.springsource.org/browse/SPR-7868
The suggestion here was to create an XML configuration for the processor and then include an #ImportResource annotation in the Java based configuration to pull it in. Well, that suggestion isn't fully accurate. You can't simply create an XML file with the configuration and pull it into the Java based configuration if you are still planning to have your configuration bootstrapped via an AnnotationConfigContextLoader. Instead, you have to revert back to loading your configuration via XML first and then creating a bean for your configuration file(s) the "old-fashion" way. For me this, was pretty trivial.
New Application Context
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
<!--
Because MapperScannerConfigurer is a BeanDefinitionRegistryPostProcessor, it cannot be
configured via #Configuration files with a #Bean annotaiton, because those files are
themselves configured via a BeanDefinitionRegistryPostProcessor which cannot spawn
another one.
-->
<bean id="myBatisMapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.gwtspringpoc.server.model.dao"/>
<property name="annotationClass" value="org.springframework.stereotype.Repository"/>
</bean>
<!--
Load the rest of our configuration via our base configuration class
-->
<bean class="com.example.gwtspringpoc.server.spring.config.ApplicationContextConfig" />
</beans>
I then bootstrap the context container the traditional way, by providing a ContextConfigLocation. This works for me because the ApplicationContextConfig that I reference in the above XML handles everything else - including component scanning which will pick up all of my other #Configuration files.
Once I did this, all of my problems went away. I was able to #Autowire the UserDao as I expected and all was wonderful.
Note:
When I tried manually defining UserDao by creating a MapperFactoryBean, like in my original question's code example, there was a UserDao bean created but it was of type MapperProxy and would not #Autowire. However, I could get it to load by name using #Repository("userDao"), for what that's worth. I believe that the MapperFactoryBean suffers from a similar problem as the MapperScannerConfigurer and is simply not compatible with #Configuration files, alas.
From mybatis.3.2.0 and mybatis-spring.1.2.0,
instead of MapperFactoryBean, you can use MapperScan for this.
#Configuration
#MapperScan("org.mybatis.spring.sample.mapper")
public class AppConfig
{
#Bean
public DataSource dataSource()
{
return new EmbeddedDatabaseBuilder().addScript("schema.sql").build();
}
#Bean
public DataSourceTransactionManager transactionManager()
{
return new DataSourceTransactionManager(dataSource());
}
#Bean
public SqlSessionFactory sqlSessionFactory() throws Exception
{
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
return sessionFactory.getObject();
}
}
Another possible solution can be found in the jira ticked that Jason mentioned. Solved my problem and I did not have to use XML configuration which I try to avoid at any cost...
https://jira.spring.io/browse/SPR-7868
#Configuration
public class A implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
#Override
public void postProcessBeanDefinitionRegistry(...) {
...
}
#Override
public void postProcessBeanFactory(...) {
...
}
#Override
public int getOrder() {
return 0;
}
}
You'll have to have a context:component-scan in your spring configurations to enable auto detection of #Component . Check the reference.