手寫MyBatis底層機制
讀取配置文件,得到數據庫連接
思路
- 引入必要的依賴
- 需要寫一個自己的config.xml文件,在里面配置一些信息,driver,url ,password,username
- 需要編寫Configuration類,對 自己的config.xml文件 進行解析,得到一個數據庫連接
實現
- 引入必要的依賴
<dependencies><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.49</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.4</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency></dependencies>
- 需要寫一個自己的config.xml文件,在里面配置一些信息,driver,url ,password,username
<?xml version="1.0" encoding="UTF-8" ?>
<database><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/hsp_mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/><property name="username" value="root"/><property name="password" value="zy"/>
</database>
- 需要編寫Configuration類,對 自己的config.xml文件 進行解析,得到一個數據庫連接
public class ZyConfiguration {//屬性 類加載器private ClassLoader classLoader =ClassLoader.getSystemClassLoader();//讀取xml文件信息并處理public Connection build(String resource) {Connection connection = null;//加載配置文件,獲取對應的InputStream流InputStream resourceAsStream =classLoader.getResourceAsStream(resource);//解析xml文件SAXReader reader = new SAXReader();try {Document document = reader.read(resourceAsStream);Element root = document.getRootElement();//解析rootElementSystem.out.println("root= "+root);return evalDataSource(root);} catch (DocumentException e) {throw new RuntimeException(e);}}//解析xml文件 并返回一個連接private Connection evalDataSource(Element node) {Iterator property = node.elementIterator("property");String driverClassName = null;String url = null;String username = null;String password = null;//遍歷node子節點 獲取屬性值while(property.hasNext()){Element pro = (Element)property.next();String name = pro.attributeValue("name");String value = pro.attributeValue("value");//判斷是否得到了name 和 valueif (name == null || value == null){throw new RuntimeException("property 節點沒有設置name 或 value屬性");}switch (name){case "driverClassName":driverClassName = value;break;case "url":url = value;break;case "username":username = value;break;case "password":password = value;break;default:throw new RuntimeException("屬性名沒有匹配到");}}Connection connection = null;try {Class.forName(driverClassName);connection = DriverManager.getConnection(url, username, password);} catch (Exception e) {throw new RuntimeException(e);}return connection;}}
編寫執行器,輸入SQL語句,完成操作
思路
- 需要寫一個實體類,對應monster表
- 編寫接口executor
- 實現接口,編寫自己的執行器
- 需要一個 自己的Configuration類 返回連接,通過連接對數據庫進行操作
實現
- 需要寫一個實體類,對應monster表
@Setter
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Monster {private Integer id;private Integer age;private String name;private String email;private Date birthday;private double salary;private Integer gender;
}
- 編寫接口executor
public interface Executor {public <T> T query(String statement,Object parameter);
}
- 實現接口,編寫自己的執行器
public class ZyExecutor implements Executor{private ZyConfiguration zyConfiguration = new ZyConfiguration();@Overridepublic <T> T query(String sql, Object parameter) {Connection connection = getConnection();//查詢返回的結果集ResultSet set = null;PreparedStatement pre = null;try {pre = connection.prepareStatement(sql);//設置參數,如果參數多,用數組處理pre.setString(1, parameter.toString());set = pre.executeQuery();//把set數據封裝到對象 -- monsterMonster monster = new Monster();//簡化處理 認為返回的結果就是一個monster記錄//遍歷結果集while(set.next()){monster.setId(set.getInt("id"));monster.setName(set.getString("name"));monster.setEmail(set.getString("email"));monster.setAge(set.getInt("age"));monster.setGender(set.getInt("gender"));monster.setBirthday(set.getDate("birthday"));monster.setSalary(set.getDouble("salary"));}return (T)monster;} catch (SQLException e) {throw new RuntimeException(e);}finally {try {if (set != null) {set.close();}if (pre != null) {pre.close();}if (connection != null) {connection.close();}} catch (Exception e) {throw new RuntimeException(e);}}}public Connection getConnection(){//Configuration類 返回連接,通過連接對數據庫進行操作return zyConfiguration.build("zy_mybatis.xml");}
}
- 需要一個 自己的Configuration類 返回連接,通過連接對數據庫進行操作
將Sqlsession封裝到執行器
思路
- 需要寫自己的Sqlsession類,它是搭建連接和執行器之間的橋梁,里面封裝有 執行器 和 配置文件 以及 操作DB 的具體方法
- 寫一個selectOne方法 ,SelectOne() 返回一條記錄,一條記錄對應一個Monster對象
實現
- 需要寫自己的Sqlsession類,它是搭建連接和執行器之間的橋梁,里面封裝有 執行器 和 配置文件 以及 操作DB 的具體方法
public class ZySqlSession {//搭建連接和執行器之間的橋梁//執行器private Executor executor = new ZyExecutor();//配置private ZyConfiguration zyConfiguration = new ZyConfiguration();//操作DB 的具體方法//SelectOne 返回一條記錄-對象public <T> T selectOne(String statement,Object parameter){return executor.query(statement,parameter);}
}
- 寫一個selectOne方法 ,SelectOne() 返回一條記錄,一條記錄對應一個Monster對象
//操作DB 的具體方法//SelectOne 返回一條記錄-對象public <T> T selectOne(String statement,Object parameter){return executor.query(statement,parameter);}
開發Mapper接口和Mapper.xml
思路
- 編寫MonsterMapper接口,里面有方法getMonsterById(Integer id)根據id返回一個monster對象
- 在resources下編寫對應的monsterMapper.xml(簡化:因為在resources 編譯時會在類路徑下比較好寫)
- monsterMapper.xml 編寫具體的sql語句,并指定語句類型,id,resultType(和原生Mybatis一樣)
實現
- 編寫MonsterMapper接口,里面有方法getMonsterById(Integer id)根據id返回一個monster對象
public interface MonsterMapper {public Monster getMonsterById(Integer id);
}
- 在resources下編寫對應的monsterMapper.xml(簡化:因為在resources 編譯時會在類路徑下比較好寫)
- monsterMapper.xml 編寫具體的sql語句,并指定語句類型,id,resultType(和原生Mybatis一樣)
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.code_study.mapper.MonsterMapper"><!-- 實現配置接口方法 getMonsterById--><select id="getMonsterById" resultType="com.code_study.entity.Monster">SELECT * FROM monster WHERE id = ?</select>
</mapper>
開發MapperBean,可以和Mapper接口相映射
思路
- 開發 Function類 ,用于記錄對應的Mapper的方法信息,比如sql類型,方法名,執行的sql語句,返回類型,入參類型
- 開發 MapperBean類,記錄接口信息和接口下的所有方法
- Function類 對應 monsterMapper.xml中的信息
- MapperBean類 對應 MonsterMapper 接口中的信息
實現
- 開發 Function類 ,用于記錄對應的Mapper的方法信息,比如sql類型,方法名,執行的sql語句,返回類型,入參類型
//對應 monsterMapper.xml中的信息
public class Function {private String sqlType;//sql類型,比如select,insert,update,deleteprivate String funcName;//方法名private String sql;//執行的sql語句private Object resultType;//返回類型private String parameterType;//入參類型
}
- 開發 MapperBean類,記錄接口信息和接口下的所有方法
//對應 MonsterMapper 接口中的信息
public class MapperBean {private String interfaceName;//接口名// 接口下的所有方法private List<Function> functions;
}
- Function類 對應 monsterMapper.xml中的信息
- MapperBean類 對應 MonsterMapper 接口中的信息
在Configuration中解析MapperXML獲取MapperBean對象
思路
- 在Configuration 添加方法readMapper(String path)
- 通過 path 讀取接口對應的Mapper方法
- 保存接口下所有的方法信息
- 封裝成 MapperBean對象
實現
- 在Configuration 添加方法readMapper(String path)
- 通過 path 讀取接口對應的Mapper方法
- 保存接口下所有的方法信息
- 封裝成 MapperBean對象
//解析MapperXML獲取MapperBean對象//path = xml的路徑+文件名 是從類的加載路徑計算的(如果放在resource目錄下 之間傳xml文件名即可)public MapperBean readMapper(String path) {MapperBean mapperBean = new MapperBean();InputStream resourceAsStream = classLoader.getResourceAsStream(path);SAXReader reader = new SAXReader();try {Document document = reader.read(resourceAsStream);Element root = document.getRootElement();String namespace = root.attributeValue("namespace");mapperBean.setInterfaceName(namespace);List<Function> list = new ArrayList<>();//保存接口下所有的方法信息//得到root的迭代器Iterator iterator = root.elementIterator();while(iterator.hasNext()){Element e = (Element)iterator.next();String sqlType = e.getName().trim();String sql = e.getText().trim();String funcName = e.attributeValue("id");String resultType = e.attributeValue("resultType");//ResultType 返回的是一個Object對象 ->反射Object instance = Class.forName(resultType).newInstance();//封裝 function 對象Function function = new Function();function.setSql(sql);function.setSqlType(sqlType);function.setFuncName(funcName);function.setResultType(instance);//將封裝好的function對象 放入 list中list.add(function);mapperBean.setFunctions(list);}} catch (Exception e) {throw new RuntimeException(e);}return mapperBean;}
動態代理Mapper方法
思路
- 在SqlSession中添加方法 getMapper 輸入一個Class類型,返回mapper的動態代理對象
- 編寫動態代理類 實現 InvocationHandler 接口
- 取出mapperBean的functions 遍歷
- 判斷 當前要執行的方法和function.getFunctionName是否一致
- 調用方法返回 動態代理對象
- 編寫SqlSessionFactory 會話工廠,可以返回SqlSession
實現
-
編寫動態代理類 實現 InvocationHandler 接口
-
在SqlSession中添加方法 getMapper 輸入一個Class類型,返回mapper的動態代理對象
//返回mapper的動態代理對象public <T> T getMapper(Class<T> clazz){return (T) Proxy.newProxyInstance(clazz.getClassLoader(),new Class[]{clazz},new ZyMapperProxy(zyConfiguration,clazz,this));}
- 取出mapperBean的functions 遍歷
- 判斷 當前要執行的方法和function.getFunctionName是否一致
- 調用方法返回 動態代理對象
public class ZyMapperProxy implements InvocationHandler {private ZySqlSession zySqlSession;private String mapperFile;private ZyConfiguration zyConfiguration;public ZyMapperProxy(ZySqlSession zySqlSession, Class clazz, ZyConfiguration zyConfiguration) {this.zySqlSession = zySqlSession;this.zyConfiguration = zyConfiguration;this.mapperFile = clazz.getSimpleName() + ".xml";}//當執行Mapper接口的代理對象方法時,會執行到invoke方法@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {MapperBean mapperBean = zyConfiguration.readMapper(this.mapperFile);//判斷是否為當前xml文件對應的接口if (!method.getDeclaringClass().getName().equals(mapperBean.getInterfaceName())){return null;}//取出mapperBean的functionsList<Function> functions = mapperBean.getFunctions();//判斷當前的mapperBean 解析對應的MapperXML后,有方法if (null != functions || 0 != functions.size()){for (Function function : functions) {//當前要執行的方法和function.getFunctionNameif (method.getName().equals(function.getFuncName())){if ("SELECT".equalsIgnoreCase(function.getSqlType())){return zySqlSession.selectOne(function.getSql(),String.valueOf(args[0]));}}}}return null;}
}
- 編寫SqlSessionFactory 會話工廠,可以返回SqlSession
public class ZySqlSessionFactory {public static ZySqlSession open(){return new ZySqlSession();}
}
測試
@Test
public void openSession(){ZySqlSession zySqlSession = ZySqlSessionFactory.openSession();System.out.println("zySqlSession= "+zySqlSession);MonsterMapper mapper = zySqlSession.getMapper(MonsterMapper.class);Monster monster = mapper.getMonsterById(1);System.out.println("monster= "+monster);
}