Don't repeat the DAO! with iBatis. Creating a spring-security UserDetailsService
based on an iBatis DAO layer to use inside an OSGi environment.
DRY
Don't repeat yourself - an implementation based on iBatis.
Inspired by Per Mellqvist's Don't repeat the DAO! I tried to implement an UserService
based on Spring's SqlMapClientDaoSupport
.
In the near future the UserService
will be used by this Pebble installation. So it feels natural to create some OSGi bundles:
util.dao
- holding the DAO interfaces and theFinderIntroductionInterceptor
util.dao.ibatis
- iBatis based implementation:IbatisCrudDaoImpl
util.hsqldb
- provides ajavax.sql.DataSource
as OSGi serviceuserservice.model
- yeah right, aUser
;-)userservice.dao
- holds the iBatis based persistence layer with theUserDao
userservice.service
- the transactional service layer:UserService
userservice.security
- a thin adapter implementation publishing the service asUserDetailsService
inside the OSGi container
An implementation of FinderIntroductionInterceptor
using annotations. Every call to a method named find*
on a type implementing CrudDao
will be intercepted and the finder code will be executed. In our case IbatisCrudDaoImpl.executeFinder()
will be called.
@Aspect
public final class FinderIntroductionInterceptor implements Advice {
@Around(value = "execution(* de.datenkollektiv.util.dao.CrudDao+.find*(..))")
public Object invoke(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
FinderExecutor finderExecutor = (FinderExecutor) proceedingJoinPoint.getThis();
// check if the called function is a finder method
String methodName = proceedingJoinPoint.getSignature().getName();
Object[] arguments = proceedingJoinPoint.getArgs();
// execute introduced method
return finderExecutor.executeFinder(methodName, arguments);
}
}
Let's have a closer look at the iBatis based CrudDao
: It extends Spring's SqlMapClientDaoSupport
and implements the needed FinderExecutor
as shown in Don't repeat the DAO! modified to use iBatis not Hibernate:
public class IbatisCrudDaoImpl<entity> extends SqlMapClientDaoSupport implements CrudDao<entity>, FinderExecutor<entity> {
// Type of the entities to persist.
private Class<entity> entityClass = null;
...
public Entity create(final Entity newEntity) {
getSqlMapClientTemplate().insert(entityName + ".create", newEntity);
return newEntity;
}
...
public final List<entity> executeFinder(final String methodName, final Object[] arguments) {
final String queryName = entityClass.getSimpleName() + "." + methodName;
if (arguments.length == 0) {
return getSqlMapClientTemplate().queryForList(queryName);
}
if (arguments.length == 1) {
if (String.class.isAssignableFrom(arguments[0].getClass())) {
return getSqlMapClientTemplate().queryForList(queryName, (String) arguments[0]);
}
}
return getSqlMapClientTemplate().queryForList(queryName, arguments);
}
}
Some of the Spring glue XML
: No surprise compared with the Hibernate implementation mentioned above.
<bean id="abstractIbatisCrudDaoImpl" class="de.datenkollektiv.util.dao.impl.IbatisCrudDaoImpl" abstract="true">
<property name="sqlMapClient">
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"
p:configLocation="classpath:userservice-sql-map-config.xml">
<property name="dataSource" ref="dataSource" />
</bean>
</property>
</bean>
<bean id="userDao" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="de.datenkollektiv.userservice.dao.UserDao" />
<property name="target">
<bean parent="abstractIbatisCrudDaoImpl">
<constructor-arg>
<value>de.datenkollektiv.userservice.model.impl.UserImpl</value>
</constructor-arg>
</bean>
</property>
</bean>
Where is the SQL? iBatis queries are defined in a sql map.
<sqlMap namespace="USERS">
...
<insert id="UserImpl.create" parameterClass="de.datenkollektiv.userservice.model.impl.UserImpl">
insert into USERS (USERNAME, EMAIL, PASSWORD, REGISTRATIONKEY, ENABLED, ROLES)
values (#username:VARCHAR#, #email:VARCHAR#, #password:VARCHAR#, #registrationKey:VARCHAR#, #enabled:BOOLEAN#, #roles:VARCHAR#)
</insert>
...
<select id="UserImpl.findByUsername" parameterClass="java.lang.String" resultMap="abatorgenerated_IbatisUserImplResult">
select USERNAME, EMAIL, PASSWORD, REGISTRATIONKEY, ENABLED, ROLES
from USERS
where USERNAME = #username:VARCHAR#
</select>
...
</sqlMap>
I will skip the boring details of the user service/security layer. Only showing the short Spring dynamic modules snippet creating the spring-security adapter simply consuming and exposing OSGi services.
<osgi:reference id="userService" interface="de.datenkollektiv.userservice.service.UserService" />
<osgi:service ref="userDetailsService" interface="org.springframework.security.userdetails.UserDetailsService" />
<bean id="userDetailsService" class="de.datenkollektiv.userservice.security.UserDetailsServiceAdapter"
autowire="constructor" />