1、引包
引入dom4j包以及數據庫連接包,我用的是mysql數據庫,因此引入mysql-connector包
2、數據庫創建
數據庫比較簡單,創建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 |
+----+---------+------+-----------+
3、模仿Mybatis編寫mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="test.dao.UserDao">
<select id="getUserInfo" resultType="test.entity.User">
select * from tb_user
where id = ?;
</select>
<update id="updateUserName">
update tb_user set name = ?
where id = ?
</update>
<insert id="insertUser">
insert into tb_user
values(?, ?, ?, ?);
</insert>
</mapper>
4、聲明查詢接口與實體類
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 + '\'' +
'}';
}
}
5、讀取mapper文件
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<String, MapperInfo> 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;
}
}
6、動態代理創建一個查詢接口
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> T getMapper(Class<T> 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;
}
}
7、測試
測試類如下
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上