I’m playing with technologies and framework for a new project: because I’m learning things probably useful for others, I’m gonna write here few notes about what I’ve learned. This is the second article of a series.

Part 2 – Introducing spring (with mybatis-spring)

2015-10-09-162228

In this article, I will set up a library for the “DAO” layer,
that will be based on the previously created library and will be used to manipulate the database in a Spring enabled application.

Technologies used:

  • Java 8
  • MyBatis 3.3
  • Spring 4.2
  • MyBatis-Spring 1.2.3

I’ve created a simple Maven project with the sctructure showed in the image. (Using Spring Tool Suite, I’ve added the Spring Nature via contextual menu, but this is not fundamental).

UPDATE

Having the it.caladyon.bit.dao.bita package in /src/test/java is wrong! This is because MybatisSpringConfig has the @ComponentScan({“it.caladyon.bit.dao.bita”}) annotation, that could lead to Spring exceptions during initialization when you add other test (with @Configuration annotated subclasses) to it; for example, I got a nice “BeanDefinitionParsingException: Configuration problem: A circular @Import has been detected” log message.

Step 1 – Spring configuration

Using the Java Config approach, the following is the configuration I wrote: it allows to load the DAO defined in the it.caladyon.bit.dao.bita package and to use the mappers in it.caladyon.db.mapper sub-packages as Spring bean.


@Configuration
@ComponentScan({"it.caladyon.bit.dao.bita"})
@MapperScan("it.caladyon.db.mapper")
public class MybatisSpringConfig {

  @Bean
  @Autowired
  public SqlSessionFactoryBean sqlSessionFactory(ApplicationContext context)
      throws Exception {
    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    sessionFactory.setDataSource(context.getBean(DataSource.class));
    sessionFactory.setTypeAliasesPackage("it.caladyon.db.model.generated.bita");
    return sessionFactory;
  }
}

I’ve found @Lazy to be mandatory, otherwise the application is unable to start because of a circular dependency between the mybatis session factory bean and the bean based on the mapper. UPDATE: I wrote a new article about this issue.

Note that @MapperScan accepts an array of package names, not only a single String.

Step 2 – Creating the DAO class

The objective of this step is to implement a simple method to select a single record from the bita.vbitac_seg table, which has a single column key of type bigint (handled by a Long in java).

This is the interface:


public interface VbitacSegDao {

  Optional<VbitacSeg> findByCseg(Long cseg);

}

and this the implementation:


@Repository("vbitacSegDao")
public class VbitacSegDaoImpl implements VbitacSegDao {

  private final Log log = LogFactory.getLog(getClass());

  @Autowired
  private VbitacSegMapper mapper;

  @Override
  public Optional<VbitacSeg> findByCseg(Long cseg) {
    Optional<VbitacSeg> rv = null;
    VbitacSegExample example = new VbitacSegExample();
    example.createCriteria().andCSegEqualTo(cseg);
    List<VbitacSeg> records = mapper.selectByExample(example);
    if (records != null) {
      if (records.size() == 1) {
        rv = Optional.of(records.get(0));
      } else if (records.size() != 0) {
        throw new IllegalStateException("Not unique id!");
      } else {
        rv = Optional.empty();
      }
    } else {
      rv = Optional.empty();
    }
    log.debug(cseg + " -> " + rv.orElse(null));
    return rv;
  }
}

The nice thing here is that mybatis-spring is responsible of managing the database session (for a select, it has to be just opened and closed, but this works for commit and rollback too).

Step 3 – Test

I have wrote a simple functional test, that connects to the database once and select a segment for three times.


public class TestFunc_VbitacSegDaoImpl_NoCache {

  @Configuration
  @Import({ MybatisSpringConfig.class, MyConf.class })
  public static class SpringifyConf {

    @Bean
    public DataSource dataSource() {
      PGSimpleDataSource ds = new PGSimpleDataSource();
      [...skip...]
      return ds;
    }

  }

  public static final Long C_SEG = 648523113755334657L;

  private final Log log = LogFactory.getLog(getClass());

  private ApplicationContext context;

  public static void main(String[] args) {
    TestFunc_VbitacSegDaoImpl_NoCache instance = new TestFunc_VbitacSegDaoImpl_NoCache();
    instance.execute(args);
  }

  private void execute(String[] args) {
    log.info("BEGIN");

    context = new AnnotationConfigApplicationContext(SpringifyConf.class);

    VbitacSegDao dao = context.getBean(VbitacSegDao.class);
    log.info("DAO: " + dao);

    log.info("Result : " + dao.findByCseg(C_SEG));
    log.info("Result : " + dao.findByCseg(C_SEG));
    log.info("Result : " + dao.findByCseg(C_SEG));

    log.info("END");
  }

}

Reading the following output, you can see that VbitacSegDaoImpl.findByCseg is called three times, returning a different instance every time.

09:50:12,621 INFO [TestFunc_VbitacSegDaoImpl_NoCache.execute] BEGIN
09:50:12,998 INFO [AnnotationConfigApplicationContext.prepareRefresh] Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@3b22cdd0: startup date [Mon Oct 12 09:50:12 CEST 2015]; root of context hierarchy
09:50:13,299 INFO [DefaultListableBeanFactory.registerBeanDefinition] Overriding bean definition for bean 'dataSource' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=testFunc_VbitacSegDaoImpl_WithCache.SpringifyConf; factoryMethodName=dataSource; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [it/caladyon/bit/dao/bita/TestFunc_VbitacSegDaoImpl_WithCache$SpringifyConf.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=testFunc_VbitacSegDaoImpl_NoCache.SpringifyConf; factoryMethodName=dataSource; initMethodName=null; destroyMethodName=(inferred); defined in it.caladyon.bit.dao.bita.TestFunc_VbitacSegDaoImpl_NoCache$SpringifyConf]
09:50:13,446 INFO [AutowiredAnnotationBeanPostProcessor.<init>] JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
09:50:15,503 INFO [TestFunc_VbitacSegDaoImpl_NoCache.execute] DAO: it.caladyon.bit.dao.bita.VbitacSegDaoImpl@78383390
09:50:15,655 DEBUG [selectByExample.debug] ==> Preparing: select c_seg, [...] from bita.vbitac_seg WHERE ( c_seg = ? )
09:50:15,689 DEBUG [selectByExample.debug] ==> Parameters: 648523113755334657(Long)
09:50:15,923 DEBUG [selectByExample.debug] <== Total: 1
09:50:15,930 DEBUG [VbitacSegDaoImpl.findByCseg] 648523113755334657 -> VbitacSeg [Hash = 2007486296, cSeg=648523113755334657, [...] ]
09:50:15,930 INFO [TestFunc_VbitacSegDaoImpl_NoCache.execute] Result : Optional[VbitacSeg [Hash = 2007486296, cSeg=648523113755334657, [...] ]]
09:50:15,939 DEBUG [selectByExample.debug] ==> Preparing: select c_seg, [...] from bita.vbitac_seg WHERE ( c_seg = ? )
09:50:15,939 DEBUG [selectByExample.debug] ==> Parameters: 648523113755334657(Long)
09:50:15,943 DEBUG [selectByExample.debug] <== Total: 1
09:50:15,944 DEBUG [VbitacSegDaoImpl.findByCseg] 648523113755334657 -> VbitacSeg [Hash = 691691381, cSeg=648523113755334657, [...] ]
09:50:15,944 INFO [TestFunc_VbitacSegDaoImpl_NoCache.execute] Result : Optional[VbitacSeg [Hash = 691691381, cSeg=648523113755334657, [...] ]]
09:50:15,952 DEBUG [selectByExample.debug] ==> Preparing: select c_seg, [...] from bita.vbitac_seg WHERE ( c_seg = ? )
09:50:15,953 DEBUG [selectByExample.debug] ==> Parameters: 648523113755334657(Long)
09:50:15,956 DEBUG [selectByExample.debug] <== Total: 1
09:50:15,957 DEBUG [VbitacSegDaoImpl.findByCseg] 648523113755334657 -> VbitacSeg [Hash = 1393828949, cSeg=648523113755334657, [...] ]
09:50:15,957 INFO [TestFunc_VbitacSegDaoImpl_NoCache.execute] Result : Optional[VbitacSeg [Hash = 1393828949, cSeg=648523113755334657, [...] ]]
09:50:15,957 INFO [TestFunc_VbitacSegDaoImpl_NoCache.execute] END
Annunci