2012年2月6日月曜日

MyBatisの検索結果としてインターフェイスのリストを返す

Guiceをシステムに採用して半年がたち、すっかりインターフェイスが体に馴染みました。
こうなってくると永続化層のMyBatisにもインターフェイスを返して欲しくなります。
しかし、MyBatisは初期状態ではデフォルトコンストラクタがないクラスには対応していません。

ObjectFactoryの実装とバインド
この問題を解決するには、ObjectFactoryを実装し、MyBatisModule#bindObjectFactoryTypeメソッドでバインドします。
ObjectFactoryへProviderを注入
せっかくGuiceを採用しているのですから、ObjectFactoryへProviderを注入し、オブジェクトの生成を委譲しましょう。
実装したものが次のクラスです。

package myamamoto.sample.mapper;
import myamamoto.sample.bean.Person;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
class BeanObjectFactory extends DefaultObjectFactory {
private static final long serialVersionUID = 1L;
private Provider<Person> personProvider;
@Inject
BeanObjectFactory(Provider<Person> personProvider) {
this.personProvider = personProvider;
}
@Override
public Object create(@SuppressWarnings("rawtypes") Class type) {
if(Person.class == type){
return this.personProvider.get();
}
return super.create(type);
}
}



package myamamoto.sample;
import java.util.List;
import myamamoto.sample.bean.BeanModule;
import myamamoto.sample.bean.Person;
import myamamoto.sample.mapper.MapperModule;
import myamamoto.sample.mapper.PersonMapper;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Provider;
public class App
{
public static void main( String[] args )
{
Injector injector = Guice.createInjector(new MapperModule(), new BeanModule());
Provider<Person> personProvider = injector.getProvider(Person.class);
PersonMapper personMapper = injector.getInstance(PersonMapper.class);
try{
personMapper.create();
Person person = personProvider.get();
person.setName("myamamoto0417");
person.setAge(29);
personMapper.insert(person);
List<Person> persons = personMapper.select();
for(Person p: persons){
System.out.println("name: " + p.getName());
System.out.println("age: " + p.getAge());
}
}catch(Exception e){
e.printStackTrace();
}finally{
personMapper.drop();
}
}
}
view raw App.java hosted with ❤ by GitHub
package myamamoto.sample.bean;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
class BeanInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 1L;
private final Map<String, Object> map = new HashMap<String, Object>();
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(this.isCopyTo(method)){
this.invokeCopyTo(method, args);
return null;
}
if(this.isGetter(method)){
return this.invokeGetter(method);
}
if(this.isSetter(method)){
this.invokeSetter(method, args);
return null;
}
return null;
}
private void invokeCopyTo(Method method, Object[] args) {
InvocationHandler h = Proxy.getInvocationHandler(args[0]);
if(!(h instanceof BeanInvocationHandler)){
throw new IllegalArgumentException();
}
BeanInvocationHandler handler = (BeanInvocationHandler) h;
handler.map.putAll(this.map);
}
private Object invokeGetter(Method method) {
String property = this.getPropertyName(method);
return this.map.get(property);
}
private void invokeSetter(Method method, Object[] args) {
String property = this.getPropertyName(method);
this.map.put(property, args[0]);
}
private boolean isCopyTo(Method method) {
return "copyTo".equals(method.getName());
}
private boolean isGetter(Method method) {
return method.getName().startsWith("get");
}
private boolean isSetter(Method method) {
return method.getName().startsWith("set");
}
private String getPropertyName(Method method){
String methodName = method.getName();
return methodName.replaceAll("^get", "").replaceAll("^set", "").toLowerCase();
}
}
package myamamoto.sample.bean;
import com.google.inject.AbstractModule;
public class BeanModule extends AbstractModule {
@Override
protected void configure() {
bind(Person.class).toProvider(PersonProvider.class);
}
}
view raw BeanModule.java hosted with ❤ by GitHub
package myamamoto.sample.mapper;
import myamamoto.sample.bean.Person;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
class BeanObjectFactory extends DefaultObjectFactory {
private static final long serialVersionUID = 1L;
private Provider<Person> personProvider;
@Inject
BeanObjectFactory(Provider<Person> personProvider) {
this.personProvider = personProvider;
}
@Override
public Object create(@SuppressWarnings("rawtypes") Class type) {
if(Person.class == type){
return this.personProvider.get();
}
return super.create(type);
}
}
package myamamoto.sample.mapper;
import java.util.Properties;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.mybatis.guice.MyBatisModule;
import org.mybatis.guice.datasource.builtin.UnpooledDataSourceProvider;
import org.mybatis.guice.datasource.helper.JdbcHelper;
import com.google.inject.name.Names;
public class MapperModule extends MyBatisModule {
@Override
protected void initialize() {
install(JdbcHelper.H2_IN_MEMORY_PRIVATE);
bindTransactionFactoryType(JdbcTransactionFactory.class);
bindDataSourceProviderType(UnpooledDataSourceProvider.class);
bindObjectFactoryType(BeanObjectFactory.class);
Properties properties = new Properties();
properties.setProperty("mybatis.environment.id", "sample");
properties.setProperty("JDBC.username", "");
properties.setProperty("JDBC.password", "");
Names.bindProperties(binder(), properties);
addMapperClass(PersonMapper.class);
}
}
package myamamoto.sample.bean;
public interface Person {
String getName();
void setName(String name);
Integer getAge();
void setAge(Integer age);
}
view raw Person.java hosted with ❤ by GitHub
package myamamoto.sample.mapper;
import java.util.List;
import myamamoto.sample.bean.Person;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
public interface PersonMapper {
static final String CREATE_SQL =
"CREATE TABLE person(name VARCHAR, age INTEGER)";
@Update(CREATE_SQL)
void create();
static final String INSERT_SQL =
"INSERT INTO person VALUES(#{name}, #{age})";
@Update(INSERT_SQL)
int insert(Person person);
static final String SELECT_SQL =
"SELECT name, age FROM person";
@Select(SELECT_SQL)
List<Person> select();
static final String DROP_SQL =
"DROP TABLE person";
@Update(DROP_SQL)
void drop();
}
package myamamoto.sample.bean;
import java.lang.reflect.Proxy;
import com.google.inject.Provider;
class PersonProvider implements Provider<Person> {
public Person get() {
return (Person) Proxy.newProxyInstance(
Person.class.getClassLoader(),
new Class[]{Person.class},
new BeanInvocationHandler());
}
}

0 件のコメント:

コメントを投稿