I'm trying to configure Spring Transactions but they seem not to work.
This is my config Java Class where I give definitions to Datasource, JdbcTemplates and all the Daos that I use
#Configuration
#PropertySource("${configuration.properties.path}")
#EnableTransactionManagement
public class BeanConfiguration {
...different DAOs...
#Override
#Bean
public DataSourceTransactionManager transactionManager(){
return new DataSourceTransactionManager(dataSource());
}
#Override
#Bean
public MyManager myManager(){
return new MyManager();
}
}
This is the class MyManager that has Transactible methods and operates with multiple DAOs.
//#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
#Transactional(propagation = Propagation.REQUIRED, rollbackFor=Exception.class)
public class EnoceanDeviceManager{
....autowired Daos
public void addNewDevices(Class1 class1, Class2 class2) {
//returns with id
class1 = 1dao.push(class1);
class2.setClass1(class1);
class2 = 2dao.push(class2); //can throw exception
}
}
I want that DB changes made with the help of 1dao do not occure if the method dao.push throws an Exception. There is no try-catches within DAO-level.
This is the class where I use one exemplar of MyManager to push a pair of objects together:
public class TestEnoceanDeviceManager extends BigClassForTestsWITHCOnfigurations{
#Atowired
MyManager myManager;
#Test
//the method
public void testAddNewDevices(){
.... defining class1 and class2....
myManager.addNewDevices(class1, class2);
}
}
As a result the method testAddNewDevices only throws a non-catched Exception from 2dao.push() method and doesn't log about rolling back. I tried to catch an Exception on different levels, but there is still no rolling back.
What am I doing wrong?
Related
I have a service with a persistence setup using JPA, Hibernate and Guice (if it's useful, I'm not using Spring). This is the first, working version of my code:
public class BookDao {
#Inject
protected Provider<EntityManager> entityManagerProvider;
protected EntityManager getEntityManager() {
return entityManagerProvider.get();
}
#Transactional
public void persist(Book book) {
getEntityManager().persist(book);
}
}
public class MyAppModule extends AbstractModule {
#Override
protected void configure() {
initializePersistence();
}
private void initializePersistence() {
final JpaPersistModule jpaPersistModule = new JpaPersistModule("prod");
jpaPersistModule.properties(new Properties());
install(jpaPersistModule);
}
}
But now I need to configure multiple persistence units. I'm following the advice in this mailing list, and according to them, I should move my module logic to a private module. I did as suggested and created a second version of the same code, the changes are commented below:
#BindingAnnotation
#Retention(RetentionPolicy.RUNTIME)
#Target({ FIELD, PARAMETER, METHOD })
public #interface ProductionDataSource {} // defined this new annotation
public class BookDao {
#Inject
#ProductionDataSource // added the annotation here
protected Provider<EntityManager> entityManagerProvider;
protected EntityManager getEntityManager() {
return entityManagerProvider.get();
}
#Transactional
public void persist(Book book) throws Exception {
getEntityManager().persist(book);
}
}
public class MyAppModule extends PrivateModule { // module is now private
#Override
protected void configure() {
initializePersistence();
// expose the annotated entity manager
Provider<EntityManager> entityManagerProvider = binder().getProvider(EntityManager.class);
bind(EntityManager.class).annotatedWith(ProductionDataSource.class).toProvider(entityManagerProvider);
expose(EntityManager.class).annotatedWith(ProductionDataSource.class);
}
private void initializePersistence() {
JpaPersistModule jpaPersistModule = new JpaPersistModule("prod");
jpaPersistModule.properties(new Properties());
install(jpaPersistModule);
}
}
The newly annotated EntityManager is being correctly injected by Guice and is non-null, but here's the fun part: some of my unit tests started failing, for example:
class BookDaoTest {
private Injector injector;
private BookDao testee;
#BeforeEach
public void setup() {
injector = Guice.createInjector(new MyAppModule());
injector.injectMembers(this);
testee = injector.getInstance(BookDao.class);
}
#Test
public void testPersistBook() throws Exception {
// given
Book newBook = new Book();
assertNull(newBook.getId());
// when
newBook = testee.persist(newBook);
// then
assertNotNull(newBook.getId()); // works in the first version, fails in the second
}
}
In the first version of my code the last line above just works: the entity is persisted and has a new id. However, in the second version of my code (using a PrivateModule and exposing an annotated EntityManager from it) the persist() operation doesn't work anymore, the entity is without an id. What could be the problem? I didn't do any other configuration changes in my environment, and I don't see error messages in the logs. Let me know if you need more details.
It turns out that the problem was the #Transactional annotation. In the first version of my code, Guice automatically adds interceptors for managing the transaction. By doing a debug, I found out that before executing my persist(Book book) method, Guice calls the following method from the com.google.inject.internal.InterceptorStackCallback package:
public Object intercept(Object proxy, Method method, Object[] arguments, MethodProxy methodProxy)
In the second version of my code, when I exposed the persistence unit from a private module the above interceptor was no longer called, leaving my persist operation without transaction handling. This is a known issue and is by design.
As a workaround I had to implement transactions by hand, making my code more verbose. I also had to change the way the entity manager is injected. This solution worked for me:
public class BookDao {
#Inject
#Named(PROD_PERSISTENCE_UNIT_NAME)
private EntityManagerFactory entityManagerFactory;
private EntityManager getEntityManager() {
return entityManagerFactory.createEntityManager();
}
public void persist(Book book) throws Exception {
EntityManager em = getEntityManager();
try {
em.getTransaction().begin();
em.persist(book);
em.getTransaction().commit();
} catch (Exception e) {
em.getTransaction().rollback();
throw e;
} finally {
em.close();
}
}
}
public class MyAppModule extends PrivateModule {
public static final String PROD_PERSISTENCE_UNIT_NAME = "prod";
#Override
protected void configure() {
initializePersistence();
}
private void initializePersistence() {
// persistence unit set to prod DB
final JpaPersistModule jpaPersistModule = new JpaPersistModule(PROD_PERSISTENCE_UNIT_NAME);
// connection properties set to suitable prod values
jpaPersistModule.properties(new Properties());
install(jpaPersistModule);
// expose bindings to entity manager annotated as "prod"
bind(JPAInitializer.class).asEagerSingleton();
bind(PersistService.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).to(PersistService.class).asEagerSingleton();
expose(PersistService.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
bind(EntityManagerFactory.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).toProvider(binder().getProvider(EntityManagerFactory.class));
expose(EntityManagerFactory.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
bind(EntityManager.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).toProvider(binder().getProvider(EntityManager.class));
expose(EntityManager.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
bind(UnitOfWork.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).toProvider(binder().getProvider(UnitOfWork.class));
expose(UnitOfWork.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
}
}
As a lesson, be very watchful around annotations and other such "magic" that modifies your code under the hood, finding bugs becomes quite difficult.
I have a test utility for with I need to have a fresh instance per test method (to prevent that state leaks between tests). So far, I was using the scope "prototype", but now I want to be able to wire the utility into another test utility, and the wired instances shall be the same per test.
This appears to be a standard problem, so I was wondering if there is a "test method" scope or something similar?
This is the structure of the test class and test utilities:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyTest {
#Autowired
private TestDriver driver;
#Autowired
private TestStateProvider state;
// ... state
// ... methods
}
#Component
#Scope("prototype") // not right because MyTest and TestStateProvider get separate instances
public class TestDriver {
// ...
}
#Component
public class TestStateProvider {
#Autowired
private TestDriver driver;
// ...
}
I'm aware that I could use #Scope("singleton") and #DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) but this refreshes more than I need – a new TestDriver instance for each test would be enough. Also, this approach is error-prone because all tests using the TestDriver would need to know that they also need the #DirtiesContext annotation. So I'm looking for a better solution.
It is actually pretty easy to implement a testMethod scope:
public class TestMethodScope implements Scope {
public static final String NAME = "testMethod";
private Map<String, Object> scopedObjects = new HashMap<>();
private Map<String, Runnable> destructionCallbacks = new HashMap<>();
#Override
public Object get(String name, ObjectFactory<?> objectFactory) {
if (!scopedObjects.containsKey(name)) {
scopedObjects.put(name, objectFactory.getObject());
}
return scopedObjects.get(name);
}
#Override
public void registerDestructionCallback(String name, Runnable callback) {
destructionCallbacks.put(name, callback);
}
#Override
public Object remove(String name) {
throw new UnsupportedOperationException();
}
#Override
public String getConversationId() {
return null;
}
#Override
public Object resolveContextualObject(String key) {
return null;
}
public static class TestExecutionListener implements org.springframework.test.context.TestExecutionListener {
#Override
public void afterTestMethod(TestContext testContext) throws Exception {
ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) testContext
.getApplicationContext();
TestMethodScope scope = (TestMethodScope) applicationContext.getBeanFactory().getRegisteredScope(NAME);
scope.destructionCallbacks.values().forEach(callback -> callback.run());
scope.destructionCallbacks.clear();
scope.scopedObjects.clear();
}
}
#Component
public static class ScopeRegistration implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
factory.registerScope(NAME, new TestMethodScope());
}
}
}
Just register the test execution listener, and there will be one instance per test of all #Scope("testMethod") annotated types:
#RunWith(SpringRunner.class)
#SpringBootTest
#TestExecutionListeners(listeners = TestMethodScope.TestExecutionListener.class,
mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
public class MyTest {
#Autowired
// ... types annotated with #Scope("testMethod")
}
I ran into the same problem some time ago and came to this solution:
Use Mocks
I wrote some methods to create specific mockito settings to add behavior to each mock.
So create a TestConfiguration class with following methods and bean definition.
private MockSettings createResetAfterMockSettings() {
return MockReset.withSettings(MockReset.AFTER);
}
private <T> T mockClass(Class<T> classToMock) {
return mock(classToMock, createResetAfterMockSettings());
}
and your bean definition will look like:
#Bean
public TestDriver testDriver() {
return mockClass(TestDriver .class);
}
MockReset.AFTER is used to reset the mock after the test method is run.
And finally add a TestExecutionListeners to your Test class:
#TestExecutionListeners({ResetMocksTestExecutionListener.class})
I have achieved using AspectJ to add #Transactional to non-Bean class and non-public methods. However, I still can't make self-invocation success.
This is my transcation manager config class
#Configuration
#EnableTransactionManagement
public class DBConfig {
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager txManager = new DataSourceTransactionManager(DATA_SOURCE);
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);
return txManager;
}
}
This is my loadtime weaver config
#Configuration
#EnableLoadTimeWeaving
public class AspectJConfig implements LoadTimeWeavingConfigurer {
#Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new InstrumentationLoadTimeWeaver();
}
}
And this is my self-invocation codes
public class Test {
#Transactional
public void testA() {
testB();
//......
}
#Transactional(propagation = Propagation.NEVER)
public void testB() {
//......
}
}
When I call testA, It's expected that it will throw exception because I have defined the propagation as NEVER. However, actually nothing happened.
So would anyone help me???
I seem to be having an issue with the way I have implemented autowiring in my Spring Batch application.For example if I use:
public class A{
#Autowired
BeanList beanList;
}
this works fine for Class A.In the sense that,beanList returns the values that it should.But if from a method from class A I am calling a method from a different class and then have the same
#Autowired
BeanList beanList
,beanList return a null.But autowiring seems to work fine across steps.I have
I'm not sure if I understood your question correctly. I assume, you have something like this:
public class A{
B aB;
#Autowired
BeanList beanList;
public void callToB() { aB.aMethod(); }
}
public class B {
#Autowired
BeanList beanList;
public void aMethod() {Assert.notNull(beanList);}
}
If this is correct, then the problem is likely that you didn't instantiate class B as a "spring bean".
The most simply way to do this would be to mark class B with #Component and to autowire it into class A.
public class A {
#Autowired
B aB;
or to instantiate it with an #Bean directly in class A
#Component
public class A {
#Autowired
BeanList beanList;
#Bean
B myBean() {return new B();}
public void callToB() { myBean().aMethod(); }
Does this describe and solve your problem?
I am practising on spring-social and it seems that the userConnectionRepository is not properly autowired in the following code when I do a "Run as Junit Test" in Eclipse. I get a Null pointer exception on the usersConnectionRepository when creating a new FacebookOffLine although breakpoints put in the #Bean java creation code shows that they seem to be properly created. Thanks in advance,
public class FacebookOffline {
private Facebook fb;
#Autowired
private UsersConnectionRepository usersConnectionRepository;
public FacebookOffline(User user) {
super();
ConnectionRepository cr = usersConnectionRepository.createConnectionRepository(user.getId());
fb = cr.getPrimaryConnection(Facebook.class).getApi();
}
}
Here is the test code :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {
org.springframework.social.quickstart.config.MainConfig.class,
org.springframework.social.quickstart.config.SocialConfig.class })
public class FacebookOfflineTest {
#Test
public void test1() {
FacebookOffline essai = new FacebookOffline(new User("yves"));
And the Spring configuration classes adapted from Keith Donald Quick Start Sample :
#Configuration
#ComponentScan(basePackages = "org.springframework.social.quickstart", excludeFilters = { #Filter(Configuration.class) })
#PropertySource("classpath:org/springframework/social/quickstart/config/application.properties")
public class MainConfig {
#Bean
public DataSource datasource() {
DriverManagerDataSource toReturn = new DriverManagerDataSource("jdbc:mysql://localhost:3306/spring_social");
toReturn.setDriverClassName("com.mysql.jdbc.Driver");
toReturn.setUsername("spring");
toReturn.setPassword("spring");
return toReturn;
}
}
#Configuration
public class SocialConfig {
#Inject
private Environment environment;
#Inject
private DataSource dataSource;
#Bean
public ConnectionFactoryLocator connectionFactoryLocator() {
ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(environment
.getProperty("facebook.clientId"), environment
.getProperty("facebook.clientSecret")));
return registry;
}
#Bean
public UsersConnectionRepository usersConnectionRepository() {
JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(
dataSource, connectionFactoryLocator(), Encryptors.noOpText());
return repository;
}
}
Actually there are 2 problems here.
Spring cannot autowire beans it doesn't control (i.e. created with new)
Dependencies aren't available in the constructor (an object instance is needed before it can be injected)
The first one can be mitigated by letting spring manage an instance of FacebookOffline (or if you need multiple instances make the bean request or session scoped).
The second is a bit harder but can probaly solved by using a method annotated with #PostConstruct (or by implementing InitializingBean from spring).
You did
FacebookOffline essai = new FacebookOffline(new User("yves"));
That means, Spring isn't managing this essai instance and thus spring can't autowire any variables in the essai.
You'll have to create bean of FacebookOffline in SocialConfig.
Then you can have
/* ... */
public class FacebookOfflineTest {
#Autowired
ApplicationContext context;
#Test
public void test1() {
FacebookOffline essai = context.getBean(FacebookOffline.class);
OR
/* ... */
public class FacebookOfflineTest {
#Autowired
FacebookOffline essai;
#Test
public void test1() {
// You can use essai now
Also, you'll need to update FacebookOffline as Dependencies ain't available in constructor.
public class FacebookOffline {
private Facebook fb;
#Autowired
private UsersConnectionRepository usersConnectionRepository;
public FacebookOffline(User user) {
super();
}
#PostConstruct
void loadFacebook() {
ConnectionRepository cr = usersConnectionRepository.createConnectionRepository(user.getId());
fb = cr.getPrimaryConnection(Facebook.class).getApi();
}
}
Spring can't autowire fields on an instance you create via new since it doesn't know about it. Declare a bean of type FacebookOffline instead.