聊聊、手写Mybatis XML配置方式
导航:
聊聊、手写Mybatis SpringBoot Starter
首先,提出一个问题,怎么通过 mapperInterface 就能拿到数据的呢?
AccountMapper.java
public interface AccountMapper {
@Select("select * from account")
public List<Map<String,Object>> queryAll();
}
在 main 方法中只要 AccountMapper mapper = (AccountMapper) getMapper(AccountMapper.class);mapper.queryAll();
这里的 mapper 如果是个接口类,怎么去调用方法?怎么实例化?所以这里的 mapper 一定是个代理类。
我们先来了解动态代理,实现动态代理的方式有很多种,例如:cglib、jdk动态代理,javassist。具体可以参考《聊聊、动态代理》。
我们来看看,Mybatis 中怎么用动态代理的,我们用 JDK动态代理 实现 mapper 代理类。
AccountInvokers.java
public class AccountInvokers implements InvocationHandler {
public AccountInvokers() {
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通过 Method 拿到 @Select 注解的 value,也就是 @Select("select * from account") 中的 SQL 语句
Select select = method.getAnnotation(Select.class);
//这里的 sql = select * from account
String sql = select.value()[0];
// 通过 sql 语句,查询数据库,没错,下面就是我们很熟悉的 JDBC 操作
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test?useSSL=false", "root", "root");
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(sql);
//封装成返回的对象类型
List<Map<String,Object>> list = new ArrayList<>();
while (rs.next()){
Map<String,Object> map = new HashMap<>();
int id = rs.getInt("id");
String name = rs.getString("NAME");
map.put("id",id);
map.put("name",name);
list.add(map);
}
st.close();
con.close();
return list;
}
}
好了,代理逻辑就是上面的代码,我们来调用看看。
Main方法
public class ManualMain {
public static void main(String[] args) {
AccountMapper mapper = (AccountMapper) getMapper(AccountMapper.class);
System.out.println(mapper.queryAll());
}
public static Object getMapper(Class clazz){
Object instance = Proxy.newProxyInstance(ManualMain.class.getClassLoader(), new Class[]{clazz}, new AccountInvokers());
return instance;
}
}
在 AccountInvokers 中,其实是有数据库连接操作逻辑的,所以我们调用 mapper.queryAll() 就能拿到数据。但这里代码还不够完善,因为并没有集成 Spring。
这里只是解决了动态代理,还没有交给 Spring 管理。如果要交给 Spring 管理,我们实现自己的 AccountMapperFactoryBean,类似 MapperFactoryBean。
AccountMapperFactoryBean
package org.rockcode.factory;
import org.rockcode.invokers.AccountInvokers;
import org.rockcode.mappers.AccountMapper;
import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.Proxy;
import org.springframework.stereotype.Component;
@Component
public class AccountMapperFactoryBean implements FactoryBean {
@Override
public Object getObject() {
return getMapper(AccountMapper.class);
}
private Object getMapper(Class clazz){
Object instance = Proxy.newProxyInstance(AccountFactoryBean.class.getClassLoader(), new Class[]{clazz}, new AccountInvokers());
return instance;
}
@Override
public Class<?> getObjectType() {
return AccountMapper.class;
}
}
AccountConfig
package org.rockcode.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({"org.rockcode.factory"})
public class AccountConfig {
}
Main方法
public class ManualMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AccountConfig.class);
AccountMapperFactoryBean accountFactoryBean = ac.getBean(AccountFactoryBean.class);
AccountMapper accountMapper = (AccountMapper) accountFactoryBean.getObject();
System.out.println(accountMapper.queryAll());
}
}
上面的 AccountMapperFactoryBean 实现了 FactoryBean,这个扩展点在于 Spring 从 getObject() 方法中获得 Bean 实体。但相比 MapperFactoryBean,还少了 mapperInterface 和 sqlSessionFactory 两个属性。
继续优化 AccountMapperFactoryBean
AccountMapperFactoryBean
package org.rockcode.factory;
import org.apache.ibatis.session.Configuration;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.beans.factory.FactoryBean;
import static org.springframework.util.Assert.notNull;
public class AccountMapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {
private Class mapperInterface;
public void setMapperInterface(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
configuration.addMapper(this.mapperInterface);
}
@Override
public Object getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<?> getObjectType() {
return this.mapperInterface;
}
}
spring.xml
<bean id="accountMapper" class="org.rockcode.factory.AccountMapperFactoryBean">
<property name="mapperInterface" value="org.rockcode.mappers.AccountMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
AccountController
@Controller
@RequestMapping
public class AccountController {
@Autowired
private AccountMapper accountMapper;
@RequestMapping("/getAllAccount")
@ResponseBody
public String getAllAccount(){
accountMapper.queryAll();
return "getAllAccount";
}
}
上面的实现用到了 FactoryBean,也用到了 SqlSessionDaoSupport,而 SqlSessionDaoSupport 中恰好用到了 InitializingBean 扩展点。
就是在 InitializingBean 的 afterPropertiesSet() 方法中,调用了 checkDaoConfig() 方法。
好了,到这里,手写 Mybatis 已经完成了一大半,实现了自己的 AccountMapperFactoryBean ,但是这个 AccountMapperFactoryBean 需要 XML 配置。如果是注解方式来实现手写 Mybatis,那有该怎么做呢?