在dao层和service层,每个子类都要自己去重复实现很多同样的内容,这个是需要优化的,武器就是泛型。
dao层改造后的结构如下:
接口<----------------------------实现类
父 BaseDao<---------------------BaseDaoImpl
^ ^
继承 UserDao<----------------------UserDaoImpl
这样,所有Dao要实现的公共接口可以定义在BaseDao中,所有Dao实现类要实现的公用方法,可以在BaseDaoImpl中实现。
PK使用泛型是考虑有些系统表的主键使用的是integer(自增或用sequence),有些系统是使用的string(多用GUID)
从第五章改造后的代码如下:
BaseDao.java
package mybatistest.dao;
import java.io.Serializable;
public interface BaseDao<T, PK extends Serializable> {
public T getByID(PK id);
}
UserDao.java
package mybatistest.dao;
import mybatistest.entity.User;
public interface UserDao extends BaseDao<User, String>{
}
BaseDaoImpl.java
package mybatistest.dao.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import mybatistest.dao.BaseDao;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public abstract class BaseDaoImpl<T, PK extends Serializable> implements
BaseDao<T, PK> {
protected SqlSessionFactory sqlSessionFactory;
public BaseDaoImpl() {
String resource = "mybatis-configuration.xml";
try {
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public T getByID(PK id) {
return null;
}
}
UserDaoImpl.java
package mybatistest.dao.impl;
import mybatistest.dao.UserDao;
import mybatistest.entity.User;
public class UserDaoImpl extends BaseDaoImpl<User, String> implements UserDao {
public UserDaoImpl(){
super();
}
public User getByID(String id) {
return sqlSessionFactory.openSession().selectOne(
"mybatistest.mapper.User.getByID",id);
}
}
Service层类似,此处不再贴出代码了。
Test.java的代码不用改,运行一下,结果同第五章。
虽然运行结果同第五章,但实际上已发生了很大的变化。因为只是举例,所以一是Dao中写的方法少,二是Dao少。当实际应用时,就会有很多的相同的方法和很多的Dao。这样,我们就可以把公共的在父接口和父实现类中定义或实现。
举个简单的例子:
上面在UserDaoImpl.java中,仍然覆写了getByID的方法,按这个思路,每个子类还都要写,那我们能不能在BaseDaoImpl中使用公共的方法来实现呢?答案是肯定的:
两种思路:
1、父类能够得到每个子类的泛型的className,就可以这样:
首先,我们要规划一下mapper中的namespace,每个mapper.xml中的namespace定义成与子类泛型具体类的全名,如user.xml改为如下:
<mapper namespace="mybatistest.entity.User">
然后,就可以在BaseDaoImpl中实现公共的方法了:
package mybatistest.dao.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import mybatistest.dao.BaseDao;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public abstract class BaseDaoImpl<T, PK extends Serializable> implements
BaseDao<T, PK> {
protected SqlSessionFactory sqlSessionFactory;
private Class<T> clazz;
public BaseDaoImpl() {
String resource = "mybatis-configuration.xml";
try {
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
this.clazz = (Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}
public T getByID(PK id) {
String methodName = Thread.currentThread().getStackTrace()[1]
.getMethodName();
return sqlSessionFactory.openSession()
.selectOne(clazz.getName() + "." + methodName, id);
}
}
这样,UserDaoImpl就可以删除getByID这个方法,使用父类的方法:
package mybatistest.dao.impl;
import mybatistest.dao.UserDao;
import mybatistest.entity.User;
public class UserDaoImpl extends BaseDaoImpl<User, String> implements UserDao {
public UserDaoImpl(){
super();
}
}
2、第二种方式父类定义一个String的mapper变量,由各子类在构造方法中,设置该子类对应的mapper的namespace,具体就不贴代码了。这种灵活性高一些,但麻烦的是每个子类都要在构造方法中去设置这个变量的值。当然,后面考虑更多的公共方法抽取到父类中实现,都可以按这种思路,比如前面说的对应的表名、主键名等,这样就可以实现一个公共的mapper文件,通过动态SQL,来实现所有的表的基本的CURD。不过这种方式是好,还是不好,可能也存在一些争论吧。
后面,就要开始学习Spring了,这块以前只是很简单的了解了一下,现在看了一下Spring,内容更多了,包括ORM、Bean的容器管理,还有WEB和MVC等,感觉是要往一整套的解决方案走的节奏啊。不过不要强大完整以后往商业化走啊。