引入dom4j包以及数据库连接包,我用的是mysql数据库,因此引入mysql-connector包
数据库比较简单,创建sql如下
CREATE DATABASE db_test;
use db_test;
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL,
`name` varchar(20) NOT NULL,
`age` tinyint(4) DEFAULT '0',
`addr` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
然后随便插入几条数据
+----+---------+------+-----------+
| id | name | age | addr |
+----+---------+------+-----------+
| 1 | Kurozaki| 19 | Guangdong |
| 2 | Kanako | 20 | Japan |
| 3 | Lee | 20 | Malaysia |
| 4 | Kintoki | 28 | Shenzhen |
+----+---------+------+-----------+
update tb_user set name = ?
where id = ?
insert into tb_user
values(?, ?, ?, ?);
package test.dao;
import test.entity.User;
/**
* Created by YotWei on 2018/8/6.
*/
public interface UserDao {
User getUserInfo(int id);
int updateUserName(String newName, int id);
int insertUser(int id, String name, int age, String addr);
}
package test.entity;
/**
* Created by YotWei on 2018/8/6.
*/
public class User {
private int id;
private String name;
private int age;
private String addr;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", addr='" + addr + '\'' +
'}';
}
}
mapper文件作用是管理sql语句与接口方法的映射,在使用Mybatis框架的时候,会先从mapper中读取映射信息,包括接口名,方法名,查询返回的数据类型,SQL语句的内容等等,MapperInfo定义如下
package com.yotwei.core;
/**
* Created by YotWei on 2018/8/6.
*/
public class MapperInfo {
private QueryType queryType;
private String interfaceName;
private String methodName;
private String sql;
private String resultType;
public QueryType getQueryType() {
return queryType;
}
public void setQueryType(QueryType queryType) {
this.queryType = queryType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public String getInterfaceName() {
return interfaceName;
}
public void setInterfaceName(String interfaceName) {
this.interfaceName = interfaceName;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
@Override
public String toString() {
return "MapperInfo{" +
"queryType=" + queryType +
", interfaceName='" + interfaceName + '\'' +
", methodName='" + methodName + '\'' +
", sql='" + sql + '\'' +
", resultType='" + resultType + '\'' +
'}';
}
}
其中QueryType是一个枚举类型
package com.yotwei.core;
/**
* Created by YotWei on 2018/8/6.
*/
public enum QueryType {
SELECT, UPDATE, INSERT, DELETE;
public static QueryType value(String v) {
return valueOf(v.toUpperCase());
}
}
下面是用一个类来读取mapper的信息,这个类可以用枚举单例实现
package com.yotwei.core;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
/**
* Created by YotWei on 2018/8/6.
*/
public enum SqlMappersHolder {
INSTANCE;
private Map mi = null;
SqlMappersHolder() {
if (mi != null)
return;
mi = new HashMap<>();
File dir = new File(SqlMappersHolder.class
.getClassLoader()
.getResource(Config.DEFAULT.getMapperPath())
.getFile());
// 用dom4j解析
SAXReader reader = new SAXReader();
try {
for (String file : dir.list()) {
Document doc = reader.read(new File(dir, file));
Element root = doc.getRootElement();
String className = root.attributeValue("namespace");
for (Object o : root.elements()) {
Element e = (Element) o;
MapperInfo info = new MapperInfo();
info.setQueryType(QueryType.value(e.getName()));
info.setInterfaceName(className);
info.setMethodName(e.attributeValue("id"));
info.setResultType(e.attributeValue("resultType"));
info.setSql(e.getText());
mi.put(idOf(className, e.attributeValue("id")), info);
}
}
} catch (DocumentException e) {
e.printStackTrace();
}
}
public MapperInfo getMapperInfo(String className, String methodName) {
return mi.get(idOf(className, methodName));
}
/*
* 类名+"."+方法名作为唯一id
*/
private String idOf(String className, String methodName) {
return className + "." + methodName;
}
}
SqlSession提供一个getMapper方法来获取一个DAO接口,DAO由代理类动态创建,传入一个核心的Sql执行类SqlExecuteHandler,该类实现InvocationHandler接口
package com.yotwei.core;
import java.lang.reflect.Proxy;
/**
* Created by YotWei on 2018/8/6.
*/
public class SqlSession {
@SuppressWarnings("unchecked")
public T getMapper(Class cls) {
return (T) Proxy.newProxyInstance(cls.getClassLoader(),
new Class[]{cls},
new SqlExecuteHandler());
}
}
SqlExecuteHandler的代码如下,它的主要任务有
1、在invoke方法中,根据传入的方法类获取接口名与方法名,进而通过SqlMappersHolder获取MapperInfo
2、根据配置连接数据库,获取到一个PreparedStatement对象
3、结合MapperInfo和参数列表设置PreparedStatement的参数,执行
4、获取执行结果,通过反射技术将查询结果映射到对应的实体类
package com.yotwei.core;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Created by YotWei on 2018/8/6.
*/
public class SqlExecuteHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// get mapper info
MapperInfo info = getMapperInfo(method);
// execute sql
return executeSql(info, args);
}
private MapperInfo getMapperInfo(Method method) throws Exception {
MapperInfo info = SqlMappersHolder.INSTANCE.getMapperInfo(
method.getDeclaringClass().getName(),
method.getName());
if (info == null) {
throw new Exception("Mapper not found for method: " +
method.getDeclaringClass().getName() + "." + method.getName());
}
return info;
}
private Object executeSql(MapperInfo info, Object[] params)
throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Object result = null;
PreparedStatement pstat = ConnectionManager.get().prepareStatement(info.getSql());
for (int i = 0; i < params.length; i++) {
pstat.setObject(i + 1, params[i]);
}
if (info.getQueryType() == QueryType.SELECT) {
ResultSet rs = pstat.executeQuery();
rs.first();
// 将查询结果映射为Java类或基本数据类型)
// 目前简化版仅支持String和int两种类型
if (rs.getMetaData().getColumnCount() == 1) {
switch (info.getResultType()) {
case "int":
result = rs.getInt(1);
break;
default:
result = rs.getString(1);
}
} else {
Class> resTypeClass = Class.forName(info.getResultType());
Object inst = resTypeClass.newInstance();
for (Field field : resTypeClass.getDeclaredFields()) {
String setterName = "set" +
field.getName().substring(0, 1).toUpperCase() +
field.getName().substring(1);
Method md;
switch (field.getType().getSimpleName()) {
case "int":
md = resTypeClass.getMethod(setterName, new Class[]{int.class});
md.invoke(inst, rs.getInt(field.getName()));
break;
default:
md = resTypeClass.getMethod(setterName, new Class[]{String.class});
md.invoke(inst, rs.getString(field.getName()));
}
}
result = inst;
}
} else {
result = pstat.executeUpdate();
}
pstat.close();
return result;
}
}
其中ConnectionManager的逻辑就是获取到一个Connection,我的逻辑比较简单,可以改用更好的方法替代,例如使用c3p0连接池。
package com.yotwei.core;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* Created by YotWei on 2018/8/7.
*/
public class ConnectionManager {
public static Connection get() throws SQLException {
return DriverManager.getConnection(
Config.DEFAULT.getUrl(),
Config.DEFAULT.getUser(),
Config.DEFAULT.getPwd()
);
}
}
Config也是我自己定义的,主要就是放一些配置,我写死在代码里了,可以改用读取配置文件的方式
package com.yotwei.core;
/**
* Created by YotWei on 2018/8/6.
*/
public class Config {
public static final Config DEFAULT = new Config();
private Config() {
}
private String url = "jdbc:mysql://localhost/db_test";
private String user = "root";
private String pwd = "root";
private String mapperPath = "mapper/";
public String getUrl() {
return url;
}
public String getUser() {
return user;
}
public String getPwd() {
return pwd;
}
public String getMapperPath() {
return mapperPath;
}
}
测试类如下
package test;
import com.yotwei.core.*;
import test.dao.UserDao;
/**
* Created by YotWei on 2018/8/6.
*/
public class TestClient {
public static void main(String[] args) {
SqlSessionFactory factory = new SqlSessionFactory();
SqlSession sqlSession = factory.openSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
System.out.println(userDao.getUserInfo(3));
}
}
User{id=3, name='Lee', age=20, addr='Malaysia'}
可以看到代理类成功创建,并且查询后成功映射了
源码在Github上
https://github.com/Kurozaki/mybatis-simple