最近在學習mybatis相關的內容,對mybatis功能的強大還有使用方便感受很強,也很想去了解他的實現原理,根據他的原理自己實現了一個簡單版的,在這個過程中也使用到了工廠設計模式、動態代理等等相關的知識,也順帶複習和學習,有不對之處,還請各位大佬多多指導。
1. 使用的xml的方式
具體配置如下:
2. 整體流程如下
public class MybatisTest {
// 入門
public static void main(String[] args) throws Exception {
// 1、 讀取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2、 創建sqlSessionFactory
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
// 3、 使用工程生產對象
SqlSession session = factory.openSession();
// 5、 使用代理對象執行方法
UserDao userDao = session.getMapper(UserDao.class);
List<User> users = userDao.findAll();
// 6、 釋放資源
for (User user : users) {
System.out.println(user.toString());
}
in.close();
}
}
3. 讀取配置信息
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
這個Resouces是自定義的一個類,用類加載器來讀取
public class Resources {
/**
* 根據輸入參數,獲取一個字節流
* @param filePath
* @return
*/
public static InputStream getResourceAsStream(String filePath) {
return Resources.class.getClassLoader().getResourceAsStream(filePath);
}
}
```java
### 4. 創建連接,與數據庫交互
1. 首先是需要一個創建連接的工廠,用戶創建連接
```java
public interface SqlSessionFactory {
SqlSession openSession();
}
這是一個接口類型的,我們需要一個實現類
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration cfg;
public DefaultSqlSessionFactory(Configuration cfg) {
this.cfg = cfg;
}
/**
* 用於創建新的數據操作對象
* @return
*/
public SqlSession openSession() {
return new DefaultSqlSession(cfg);
}
}
我們通過SqlSessionFactoryBuilder來創建sqlSessiongFactory
public class SqlSessionFactoryBuilder {
/**
* 根據字節輸入流來
* @param in
* @return
*/
public static SqlSessionFactory build(InputStream in) {
Configuration cfg = XMLConfigBuilder.loadConfiguration(in);
return new DefaultSqlSessionFactory(cfg);
}
}
創建工廠時使用到的Configuration,是我們定義的一個類,裏面保存着數據連接相關的屬性信息
public class Configuration {
// 驅動
private String driver;
// url
private String url;
// username
private String username;
// password
private String password;
// 存儲dao中相關操作的信息
private Map<String, Mapper> mappers;
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Map<String, Mapper> getMappers() {
return mappers;
}
public void setMappers(Map<String, Mapper> mappers) {
this.mappers.putAll(mappers);
}
}
XMLConfigBuilder這是一個工具類,用於實現對xml的解析,使用dom4j+xpath方式,也可以通過其他方式,這邊就不展示了。
2. 創建好sqlSessionFactory之後,我們就需要創建連接
public interface SqlSession {
/**
* 根據參數創建代理對象
* @param daoInterfaceClass dao接口的字節碼
* @param <T>
* @return
*/
<T> T getMapper(Class<T> daoInterfaceClass);
/**
* 釋放資源
*/
void close();
}
SqlSession中主要包括兩個方法,getMapper是根據參數創建代理對象,close用於釋放資源,有一個默認實現類
public class DefaultSqlSession implements SqlSession {
private Configuration cfg;
private Connection connection;
public DefaultSqlSession(Configuration cfg) {
this.cfg = cfg;
connection = DataSourceUtil.getConnection(cfg);
}
public <T> T getMapper(Class<T> daoInterfaceClass) {
return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(), new Class[]{daoInterfaceClass}, new MapperProxy(cfg.getMappers(), connection));
}
public void close() {
if (connection != null) {
try {
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
裏面主要有兩個屬性,一個是加載的數據庫配置信息,還有是Connection屬性。
Connection在我們構造的時候通過DataSourceUtil工具類實現
public class DataSourceUtil {
public static Connection getConnection(Configuration cfg) {
try {
Class.forName(cfg.getDriver());
return DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
在SqlSession中創建代理對象進行方法增強時,我們實現的MapperProxy
public class MapperProxy implements InvocationHandler {
// Map的key是全限定類名+方法名
private Map<String, Mapper> mappers;
private Connection conn;
public MapperProxy(Map<String, Mapper> mappers, Connection conn) {
this.mappers = mappers;
this.conn = conn;
}
/**
* 用此方法進行增強,我們的增強就是調用selectList方法
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1、獲取方法名
String methodName = method.getName();
// 2、獲取方法所在類的名稱
String className = method.getDeclaringClass().getName();
// 3、組合key
String key = className + "." + methodName;
// 4、獲取mapper中的key的value
Mapper mapper = mappers.get(key);
// 5、是否有mapper
if (mapper == null) {
throw new IllegalArgumentException("傳入的參數有誤");
}
// 6、調用工具類,查詢所有
return new Executor().selectList(mapper, conn);
}
}
Executor是一個工具類,裏面封裝類selectList方法
public class Executor {
public <E> List<E> selectList(Mapper mapper, Connection conn) {
PreparedStatement pstm = null;
ResultSet rs = null;
try {
// 1、取出mapper中的數據
String queryString = mapper.getQueryString();
String resultType = mapper.getResultType();
Class domainClass = Class.forName(resultType);
// 2、獲取PreparedStatement對象
pstm = conn.prepareStatement(queryString);
// 3、執行sql語句,獲取結果集
rs = pstm.executeQuery();
// 4、封裝結果集
List<E> list = new ArrayList<E>();
while (rs.next()) {
E obj = (E) domainClass.newInstance();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
// 獲取每列的列名,序號從1開始
String columnName = rsmd.getColumnName(i);
// 根據列名,獲取值
Object columnValue = rs.getObject(columnName);
// 給obj賦值
PropertyDescriptor pd = new PropertyDescriptor(columnName, domainClass);
// 獲取寫入方法
Method writeMethod = pd.getWriteMethod();
// 賦值對象
writeMethod.invoke(obj, columnValue);
}
list.add(obj);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
release(pstm, rs);
}
return null;
}
private void release(PreparedStatement pstm, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (pstm != null) {
try {
pstm.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
5. 接下來就是用代理對象執行方法,封裝返回類型,並釋放資源
// 5、 使用代理對象執行方法
UserDao userDao = session.getMapper(UserDao.class);
List<User> users = userDao.findAll();
// 6、 釋放資源
for (User user : users) {
System.out.println(user.toString());
}
in.close();