Hi All,
This may be a nube question, as I'm a MyBatis nube :)
I want to have a JUnit test call a transaction-bound method on a 'repo' (aka DAO) class, then roll back the changes after the test. That is, I want my whole test method to be in a transaction so it's side affects never commit.
My app is set up to use Guice, so I'm using the @Transactional annotation from mybatis-guice to wrap multiple calls to a Mapper. Like so….
/* ===== from my 'repo' class ============*/
@Override
@Transactional(
executorType = ExecutorType.REUSE,
isolationLevel = TransactionIsolationLevel.REPEATABLE_READ,
exceptionMessage = "Something went wrong while updating ECU {0}"
)
public Integer addUser(User user) {
if (user != null && userMapper.addUser(user) > 0) {
userMapper.deleteUserProducts(user);
if (user.getProducts() != null && !user.getProducts().isEmpty()) {
userMapper.addUserProducts(data);
}
return user.getUserInstanceId();
} else {
return null;
}
}
I based my test on SimpleBasicTest : http://code.google.com/p/mybatis/source/browse/sub-projects/mybatis-guice/trunk/src/test/java/org/mybatis/guice/sample/SampleBasicTest.java?r=3712
But it drops and creates the DB each setup, which is kinda slow.
I've tried a few tricks but haven't found
/* ===== test class ============*/
public class MyTest {
private Injector injector;
private UsersRepo usersRepo;
@Before
public void setup() throws Exception {
// bindings
this.injector = createInjector(new MyBatisModule() {
@Override
protected void initialize() {
install(JdbcHelper.MySQL);
bindDataSourceProviderType(PooledDataSourceProvider.class);
bindTransactionFactoryType(JdbcTransactionFactory.class);
addMapperClass(UserMapper.class);
bindProperties(binder(), createTestProperties());
bind(UsersRepo.class).to(UsersRepoMyBatis.class);
}
}
);
this.usersRepo = this.injector.getInstance(UsersRepo.class);
}
protected static Properties createTestProperties() {
Properties props = new Properties();
props.setProperty("mybatis.environment.id", "test");
props.setProperty("JDBC.host", "localhost");
props.setProperty("JDBC.port", "3306");
props.setProperty("JDBC.schema", "myapp_test");
props.setProperty("JDBC.username", "myapp_test");
props.setProperty("JDBC.password", "changeme");
props.setProperty("JDBC.autoCommit", "false");
return props;
}
/** I'd like this whole test to be in a transaction that doesn't commit. */
@Test
public void testIt() throws Exception {
// Want to start transaction here… via annotation, code, whatever
assertEquals(0, usersRepo.getAllUsers().size());
User user = new User();
user.setName("Mark Twain");
user.setProducts(null);
int r = usersRepo.addUser(user);
assertEquals(1, r);
assertEquals(0, usersRepo.getAllUsers().size());
// … and end transaction here with nothing committed.
// But since there's no transaction the test only passes once. :(
}
}
Many thanks in advance for any guidance.
,boz
-----Message d'origine-----
De : mybati...@googlegroups.com [mailto:mybati...@googlegroups.com] De la part de Poitras Christian
Envoyé : July-19-11 9:20 AM
À : 'mybati...@googlegroups.com'
Objet : RE: Noobie questions - inability to share SqlSessions, etc
This code is not optimal, but it should get you started.
To use the Runner, simply add a @RunWith annotation on your test class like this:
@RunWith(ServiceTestRollbackRunner.class)
Be aware that @Before and @After will run outside the transaction scope.
The DatabaseCleaner class in ServiceTestRollbackRunner is used to bring the database to a known state before any tests are run.
Christian
-----Message d'origine-----
De : mybati...@googlegroups.com [mailto:mybati...@googlegroups.com] De la part de Craig Envoyé : July-19-11 2:31 AM À : mybatis-user Objet : Noobie questions - inability to share SqlSessions, etc
Hi,
I have a noobie question - answers or refs to previous threads much
appreciated:
The 3.0 doco states that SqlSessions should not be shared (between threads). Is this strictly true (due to some internal usage of thread locals perhaps), or is sharing just frowned upon since SqlSessions are not thread-safe?
Ideally, I would like to be able to use different threads at different times (i.e mutual exclusion) to invoke actions on a SqlSession, potentially even within a single database transaction...
Hi,
1)
The code I supplied was incomplete.
Obviously, you need to add your modules to the line:
injector = Guice.createInjector(
Once the injector has been created, your test class dependencies will be injected automatically by the Runner.
2)
The binding done on @Transactional is used to flush the cache.
Test case example:
1) Select a value – value is put in cache
2) Update this value
3) Select same value
At step 3, you would get the old value if the cache is not flushed. So that’s why the interceptor will flush the cache each time it finds @Transactional.
If you use JDBC transactions, make sure you use @Transactional instead of sqlSession.commit() in your DAOs or the transaction will be committed and you will lose all advantages.
A workaround would be to use managed transactions.
Christian
De : mybati...@googlegroups.com [mailto:mybati...@googlegroups.com] De la part de Christofer Jennings
Envoyé : July-19-11 2:49 PM
À : mybati...@googlegroups.com
Objet : Re: RE: How to rollback all changes in a JUnit test?
public ServiceTestRollbackRunner(final Class<?> clazz) throws InitializationError {
super(clazz);
injector = Guice.createInjector(
new MyBatisModule() {
@Override
protected void initialize() {
install(JdbcHelper.MySQL);
bindDataSourceProviderType(PooledDataSourceProvider.class);
bindTransactionFactoryType(JdbcTransactionFactory.class);
Names.bindProperties(binder(), createTestProperties());
requestStaticInjection(clazz);
ClearSqlSessionCacheInterceptor clearSqlSessionCacheInterceptor = new
ClearSqlSessionCacheInterceptor();
requestInjection(clearSqlSessionCacheInterceptor);
bindInterceptor(Matchers.any(), Matchers.annotatedWith(Transactional.class),
clearSqlSessionCacheInterceptor);
addMapperClass(UserMapper.class);
bind(UserRepo.class).to(UserRepoMyBatis.class);
}
}
);
injector.injectMembers(this);
}
Personally, I don't need to do some bindings in my test classes since I group my test classes that have the same Guice config.
So I have about 5 runners with 5 different configs.
If you do need to make some binding in your test class, you can create a child injector based on the Runner's injector.
Christian
________________________________________
De : mybati...@googlegroups.com [mybati...@googlegroups.com] de la part de Christofer Jennings [boz....@gmail.com]
Date d'envoi : jeudi 21 juillet 2011 03:32
À : mybati...@googlegroups.com
Objet : Re: RE: RE: How to rollback all changes in a JUnit test?
Runs test methods within a MyBatis-controlled SQL transaction and rolls back on test completion (failed or success). Test class must also have @MapperClasses defined or an InitializationException will be thrown. If a DAO interface is used then the interface must have @ImplementedBy defined. A mybatis_test.properties file must be in the root your test's classpath.
Example:@RunWith(MyBatisTestRollbackRunner.class) @MapperClasses({UserMapper.class, AddressMapper.class}) public class UserDaoTest { @Inject private UserDao userDao; @Test ... } @ImplementedBy(UserDaoMyBatis.class) public interface UserDao { ... } public class UserDaoMyBatis implements UserDao { @Inject private UserMapper userMapper; @Inject private AddressMapper addressMapper; ... } #mybatis_test.properties mybatis.environment.id=test JDBC.host=localhost JDBC.port=3306 JDBC.schema=myschema_test JDBC.username=myschema_tester JDBC.password=password
@RunWith(MyBatisTestRollbackRunner.class) public class UserDaoTest implements MyBatisModuleProvider { @Inject private UserDao userDao; @Override public MyBatisModule getMyBatisModule() { return new PooledJdbcMyBatisModule() { @Override public void initializeForTest() { addMapperClass(UserMapper.class); addMapperClass(AddressMapper.class); } }; } @Test ... } @ImplementedBy(UserDaoMyBatis.class) public interface UserDao { ... } public class UserDaoMyBatis implements UserDao { @Inject private UserMapper userMapper; @Inject private AddressMapper addressMapper; ... }