您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關(guān)Mybatis怎么實現(xiàn)動態(tài)增刪改查功能的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
最近在看 Mybatis 的源碼,大致了解整個框架流程后便手寫了一個特別簡單的SimpMybatis
的小Demo,來鞏固這整個框架的學(xué)習。下圖是我所畫的框架大致執(zhí)行流程:
對上圖分析后得出結(jié)論:
1.Mybatis 的配置文件分為兩種,且這兩個配置文件會被封裝到 Configuration 中
主配置文件(MybatisConfig.xml):配置 jdbc 等環(huán)境信息,全局唯一;
映射文件(xxxMapper.xml):配置多個 Sql ,可有多個。
2.通過 Mybatis 配置文件得到 SqlSessionFactory ;
3.通過 SqlSessionFactory 得到 SqlSession,它就相當于 Request 請求;
4.SqlSession 調(diào)用底層的 Executor 執(zhí)行器來操作數(shù)據(jù)庫,同時執(zhí)行器有兩類實現(xiàn)
基本實現(xiàn)
帶有緩存功能的實現(xiàn)
5.解析傳入的參數(shù),對其進行封裝,執(zhí)行并返回結(jié)果;
以上就是我梳理的 Mybatis 大致流程,看似簡單,卻很精妙。
從圖中可以看出,MyConfig 負責與人交互。待讀取xml后,將屬性和連接數(shù)據(jù)庫的操作封裝在 MyConfig 對象中供后面的組件調(diào)用。本項目將使用 dom4j 來讀取xml文件,它具有性能優(yōu)異和非常方便使用的特點。
從流程圖中的箭頭可以看出,MySqlSession 的成員變量中必須得有 MyExecutorImpl 和 MyConfig 去集中做調(diào)配。一個Session僅擁有一個對應(yīng)的數(shù)據(jù)庫連接。類似于一個前段請求Request,它負責直接調(diào)用對應(yīng) execute(sql) 來做 CRUD 操作。
MyExecutor 是一個執(zhí)行器,負責SQL語句的生成和查詢緩存的維護,也就是 Jdbc 的代碼將在這里完成,不過本文只實現(xiàn)了單表,查詢緩存并未實現(xiàn)。
只是希望對指定的接口生成一個對象,使得執(zhí)行它的時候能運行一句 sql,而接口無法直接調(diào)用方法,所以這里使用動態(tài)代理生成對象,在執(zhí)行時還是回到 MySqlSession 中調(diào)用查詢,最終由 MyExecutorImpl 做 JDBC查詢。這樣設(shè)計是為了單一職責,可擴展性更強。
這次會將其打成 Jar 包,并將其導(dǎo)入項目實現(xiàn),做一個 Mybatis 的還原。
工程文件及目錄:
Maven 導(dǎo)入如下:
<!-- https://mvnrepository.com/artifact/org.dom4j/dom4j --> <!-- xml解析 --> <dependency> <groupId>org.dom4j</groupId> <artifactId>dom4j</artifactId> <version>2.1.3</version> </dependency> <!-- Mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency>
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 12:17 */ public class MyConfig { /** * 啟動應(yīng)用程序類加載器 */ private static final ClassLoader loader = ClassLoader.getSystemClassLoader(); /** * 數(shù)據(jù)庫建立連接 * @return 返回數(shù)據(jù)庫連接對象 * */ public Connection build() { // Mybatis主配置文件名 String resource = "mybatis-config.xml"; // 獲取文件根節(jié)點 Element root = parseXML(resource); // 獲取文件對應(yīng)的信息 Map<String, String> jdbcMap = parseNodes(root); try { Class.forName(jdbcMap.get("driverClassName")); } catch (ClassNotFoundException e) { throw new RuntimeException("驅(qū)動器未找到,請重新檢查!"); } Connection connect = null; try { connect = DriverManager.getConnection(jdbcMap.get("url"), jdbcMap.get("username"), jdbcMap.get("password")); } catch (SQLException throwables) { throw new RuntimeException("數(shù)據(jù)庫連接錯誤,請檢查路徑、用戶名、密碼是否輸入正確!"); } return connect; } /** * 解析數(shù)據(jù)庫配置文件 * @param resource 數(shù)據(jù)庫配置文件路徑 * @return 獲取到的文件根節(jié)點 */ public static Element parseXML(String resource) { try { // 返回用于讀取指定資源的輸入流 InputStream stream = loader.getResourceAsStream(resource); // 使用dom4j解析XML SAXReader reader = new SAXReader(); // 使用SAX從給定流中讀取文件 Document doc = reader.read(stream); // 獲取文件的根節(jié)點 return doc.getRootElement(); } catch (DocumentException e) { throw new RuntimeException("解析 XML 時發(fā)生錯誤!" + resource); } } /** * 解析主xml文件標簽節(jié)點 * @param node 配置文件根節(jié)點 * @return 返回從配置文件中拿到的開啟數(shù)據(jù)庫對應(yīng)值 */ private Map<String, String> parseNodes(Element node) { // 判斷根標簽名稱 if (!node.getName().equals("database")) { throw new RuntimeException("數(shù)據(jù)庫配置文件根標簽名稱必須為【database】"); } // 存放配置文件取得的值 Map<String, String> map = new HashMap<String, String>(); map.put("driverClassName", null); map.put("url", null); map.put("username", null); map.put("password", null); // 讀取property的屬性內(nèi)容 for (Element item : node.elements()) { // 獲取標簽中存放的值,并刪除其前導(dǎo)和結(jié)尾的空格 String value = getValue(item); // 獲取標簽中 name 的名稱 String name = item.attributeValue("name"); // 如果name或value為空則有對應(yīng)值未輸入 if (name == null || "".equals(value)) { throw new RuntimeException("[database]: <property> 中應(yīng)該包含名稱和值"); } switch (name) { case "driverClassName" : map.put("driverClassName", value); break; case "url" : map.put("url", value); break; case "username" : map.put("username", value); break; case "password" : map.put("password", value); break; default: throw new RuntimeException("[database]: <property> 中有未知屬性"); } } return map; } /** * 獲取property屬性中的值 * @param node 配置文件根節(jié)點 * @return 如果有value值,則讀取;沒有設(shè)置value,則讀取內(nèi)容 */ private static String getValue(Element node) { return node.hasContent() ? node.getText().trim() : node.attributeValue("value").trim(); } /** * * @param path * @return */ @SuppressWarnings(value = "rawtypes") public MappingBean readMapper(String path) { MappingBean bean = new MappingBean(); try { InputStream stream = loader.getResourceAsStream(path); SAXReader reader = new SAXReader(); Document doc = reader.read(stream); Element root = doc.getRootElement(); // 把mapper節(jié)點的nameSpace值存為接口名 bean.setInterfaceName(root.attributeValue("nameSpace").trim()); // 用來存儲方法的List List<Mapping> list = new ArrayList<Mapping>(); //遍歷根節(jié)點下所有子節(jié)點 for(Iterator rootIter = root.elementIterator(); rootIter.hasNext();) { // 存儲一條方法的信息 Mapping fun = new Mapping(); Element e = (Element) rootIter.next(); String sqlType = e.getName().trim(); String funcName = e.attributeValue("id").trim(); String sql = e.getText().trim(); String resultType = e.attributeValue("resultType").trim(); fun.setSqlType(sqlType); fun.setFuncName(funcName); Object newInstance = null; try { newInstance = Class.forName(resultType).newInstance(); } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e1) { e1.printStackTrace(); } fun.setResultType(newInstance); fun.setSql(sql); list.add(fun); } bean.setList(list); } catch (DocumentException e) { e.printStackTrace(); } return bean; } /** * 解析mapper映射xml文件 * @param element mapper文件路徑 * @return */ public MappingBean parseMapper(Element element) { MappingBean bean = new MappingBean(); String namespace = element.attributeValue("namespace"); if (namespace == null) { throw new RuntimeException("映射文件namespace不存在"); } bean.setInterfaceName(namespace); List<Mapping> list = new ArrayList<>(); Iterator<Element> it = element.elementIterator(); while (it.hasNext()) { Element ele=(Element) it.next(); Mapping mapping =new Mapping(); String funcName =ele.attributeValue("id"); if (funcName==null){ throw new RuntimeException("mapper映射文件中id不存在"); } String sqlType = ele.getName(); String paramType = ele.attributeValue("parameterType"); String resultType=ele.attributeValue("resultType"); String sql=ele.getText().trim(); mapping.setFuncName(funcName); mapping.setSqlType(sqlType); mapping.setParameterType(paramType); mapping.setSql(sql); Object object=null; try { object=Class.forName(resultType).newInstance(); } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { e.printStackTrace(); } mapping.setResultType(object); list.add(mapping); } bean.setList(list); return bean; } }
?由 MyConfig類 代碼可以得知:
Mybatis 主配置類名稱必須為:mybatis-config.xml
;
mybatis-config.xml 的根標簽必須為: <database></database>
;
Mapper.xml 必須包括:namespace
;
Sql 是否有返回值都應(yīng)包括:resultType
(個人偷懶,沒做判斷);... ...
MySqlSession 肯定不會自己去執(zhí)行,因為不能寫死所以使用動態(tài)代理來使代理類去實現(xiàn)具體方法。
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 12:52 */ public class MySqlSession { private final MyExcutor excutor= new MyExcutorImpl(); private final MyConfig config = new MyConfig(); public <T> T selectValue(Mapping statement, List<Object> parameter){ return excutor.queryValue(statement, parameter); } public <T> T selectNull(Mapping statement){ return excutor.queryNull(statement); } public int deleteValue(Mapping statement, List<Object> parameter) { return excutor.deleteValue(statement, parameter); } public int updateValue(Mapping statement, List<Object> parameter) { return excutor.updateValue(statement, parameter); } public int insertValue(Mapping mapping, List<Object> parameter) { return excutor.insertValue(mapping, parameter); } @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> clas){ //動態(tài)代理調(diào)用 return (T) Proxy.newProxyInstance(clas.getClassLoader(),new Class[]{clas}, new MySqlSessionProxy(config,this)); } }
編寫代理類,把mapper映射文件解析進來
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 12:55 */ public class MySqlSessionProxy implements InvocationHandler { private MyConfig config; private MySqlSession sqlSession; public MySqlSessionProxy(MyConfig config, MySqlSession sqlSession) { this.config = config; this.sqlSession = sqlSession; } @Override public Object invoke(Object proxy, Method method,Object[] args) { String name = method.getDeclaringClass().getName(); String mapperName = name.substring(name.lastIndexOf(".")+1); MappingBean bean=config.parseMapper(MyConfig.parseXML(mapperName+".xml")); if (bean!=null && (bean.getList()!=null && bean.getList().size()>0)){ for (Mapping mapping : bean.getList()){ if (mapping.getFuncName().equals(method.getName())) { // 判斷是否為查詢語句 if ("select".equals(mapping.getSqlType().toLowerCase())) { System.out.println("執(zhí)行查詢方法:" + mapping.getSql()); if (args!=null) { System.out.println("參數(shù):"+ Arrays.toString(args)); return sqlSession.selectValue(mapping, Arrays.asList(args)); } else { System.out.println("參數(shù):null"); return sqlSession.selectNull(mapping); } } // 判斷是否為刪除語句 if ("delete".equals(mapping.getSqlType().toLowerCase())){ System.out.println("執(zhí)行查詢方法:"+mapping.getSql()); System.out.println("參數(shù):"+ Arrays.toString(args)); return sqlSession.deleteValue(mapping, Arrays.asList(args)); } // 判斷是否為更新語句 if ("update".equals(mapping.getSqlType().toLowerCase())) { System.out.println("執(zhí)行查詢方法:"+mapping.getSql()); System.out.println("參數(shù):"+ Arrays.toString(args)); return sqlSession.updateValue(mapping, Arrays.asList(args)); } // 判斷是否為插入語句 if ("insert".equals(mapping.getSqlType().toLowerCase())) { System.out.println("執(zhí)行查詢方法:" + mapping.getSql()); System.out.println("參數(shù):" + Arrays.toString(args)); return sqlSession.insertValue(mapping, Arrays.asList(args)); } } } } return null; } }
?注意:通過上段代碼可知,映射文件必須和接口名稱保持一致。
a. 接口實體類
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 12:38 */ public class MappingBean { /** * 接口名 */ private String interfaceName; /** * 接口下所有方法 */ private List<Mapping> list; // setter、getter略 }
b. 映射文件中 Sql 的實體類
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 12:38 */ public class Mapping { private String sqlType; private String funcName; private String sql; private Object resultType; private String parameterType; // setter、getter略 }
MyExcutor 接口
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 12:42 */ public interface MyExcutor { // 無參查詢 <T> T queryNull(Mapping mapping); // 有參查詢 <T> T queryValue(Mapping mapping, List<Object> params); // 刪除 int deleteValue(Mapping mapping, List<Object> params); // 更新 int updateValue(Mapping mapping, List<Object> params); // 插入 int insertValue(Mapping mapping, List<Object> params); }
MyExcutorImpl 實現(xiàn)類
這里通過反射將結(jié)果轉(zhuǎn)換成對象
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 12:42 */ public class MyExcutorImpl implements MyExcutor { private MyConfig config = new MyConfig(); @Override public <T> T queryNull(Mapping mapping) { Connection conn = config.build(); PreparedStatement preparedStatement; ResultSet resultSet; Object obj; List<Object> list = new ArrayList<>(); try { preparedStatement=conn.prepareStatement(mapping.getSql()); if (mapping.getResultType() == null){ throw new RuntimeException("返回的映射結(jié)果不能為空!"); } resultSet = preparedStatement.executeQuery(); int row = 0; ResultSetMetaData rd = resultSet.getMetaData(); while (resultSet.next()){ obj=resultToObject(resultSet,mapping.getResultType()); row++; list.add(obj); } System.out.println("記錄行數(shù):"+row); } catch (SQLException e) { e.printStackTrace(); } return (T) list; } @Override public <T> T queryValue(Mapping mapping, List<Object> params) { Connection conn = config.build(); PreparedStatement preparedStatement; ResultSet resultSet; Object obj; List<Object> list = new ArrayList<>(); try { preparedStatement=conn.prepareStatement(mapping.getSql()); for (int i=0; i<params.size(); i++) { preparedStatement.setString(i+1, params.get(i).toString()); } if (mapping.getResultType() == null){ throw new RuntimeException("返回的映射結(jié)果不能為空!"); } resultSet = preparedStatement.executeQuery(); int row = 0; ResultSetMetaData rd = resultSet.getMetaData(); while (resultSet.next()){ obj=resultToObject(resultSet,mapping.getResultType()); row++; list.add(obj); } System.out.println("記錄行數(shù):"+row); } catch (SQLException e) { e.printStackTrace(); } return (T) list; } @Override public int deleteValue(Mapping mapping, List<Object> params) { Connection conn = config.build(); int rows = 0; PreparedStatement preparedStatement=null; try { preparedStatement = conn.prepareStatement(mapping.getSql()); for (int i=0; i<params.size(); i++) { preparedStatement.setString(i+1, params.get(i).toString()); } rows = preparedStatement.executeUpdate(); if (rows != 0) { System.out.println("刪除成功,受影響行數(shù):"+rows); } else { System.out.println("刪除失敗,數(shù)據(jù)庫無相應(yīng)數(shù)據(jù)..."); } } catch (SQLException e) { e.printStackTrace(); } return rows; } @Override public int updateValue(Mapping mapping, List<Object> params) { Connection conn = config.build(); int rows = 0; PreparedStatement preparedStatement=null; try { preparedStatement = conn.prepareStatement(mapping.getSql()); for (int i=0; i<params.size(); i++) { preparedStatement.setString(i+1, params.get(i).toString()); } rows = preparedStatement.executeUpdate(); if (rows != 0) { System.out.println("修改成功,受影響行數(shù):"+rows); } else { System.out.println("修改失敗,數(shù)據(jù)庫無相應(yīng)數(shù)據(jù)..."); } } catch (SQLException e) { e.printStackTrace(); } return rows; } @Override public int insertValue(Mapping mapping, List<Object> params) { Connection conn = config.build(); int rows = 0; PreparedStatement preparedStatement=null; try { preparedStatement = conn.prepareStatement(mapping.getSql()); for (int i=0; i<params.size(); i++) { preparedStatement.setString(i+1, params.get(i).toString()); } try { rows = preparedStatement.executeUpdate(); if (rows != 0) { System.out.println("插入成功,受影響行數(shù):"+rows); } else { System.out.println("插入失敗..."); } } catch (SQLException throwables) { throw new RuntimeException("插入重復(fù) \"Key\" 值數(shù)據(jù)"); } } catch (SQLException e) { e.printStackTrace(); } return rows; } private <T> T resultToObject(ResultSet rs, Object object) { Object obj=null; try { Class<?> cls = object.getClass(); /* 這里為什么要通過class再new一個對象? 因為如果不new一個新的對象,每次返回的都是形參上的object, 而這個object都是同一個,會導(dǎo)致list列表后面覆蓋前面值。 */ obj=cls.newInstance(); //獲取結(jié)果集元數(shù)據(jù)(獲取此 ResultSet 對象的列的編號、類型和屬性。) ResultSetMetaData rd=rs.getMetaData(); for (int i = 0; i < rd.getColumnCount(); i++) { //獲取列名 String columnName=rd.getColumnLabel(i+1); //組合方法名 String methodName="set"+columnName.substring(0, 1).toUpperCase()+columnName.substring(1); //獲取列類型 int columnType=rd.getColumnType(i+1); Method method=null; switch(columnType) { case java.sql.Types.VARCHAR: case java.sql.Types.CHAR: method=cls.getMethod(methodName, String.class); method.invoke(obj, rs.getString(columnName)); break; case java.sql.Types.INTEGER: method=cls.getMethod(methodName, Integer.class); method.invoke(obj, rs.getInt(columnName)); break; default: break; } } } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException | SQLException e) { e.printStackTrace(); } return (T) obj; } }
<!-- https://mvnrepository.com/artifact/org.dom4j/dom4j --> <!-- xml解析 --> <dependency> <groupId>org.dom4j</groupId> <artifactId>dom4j</artifactId> <version>2.1.3</version> </dependency> <!-- Mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> <!-- 自己寫的Mybatis,首先要將其放入本地倉庫 --> <dependency> <groupId>top.kk233</groupId> <artifactId>SimpMybatis</artifactId> <version>1.0.0</version> </dependency>
?Maven導(dǎo)入本地Jar包方法自行百度,這里就不贅述。
這里提供一個我測試的,你們可以自行創(chuàng)建其他的
CREATE DATABASE IF NOT EXISTS `test`; USE `test`; CREATE TABLE `user` ( `id` INT ( 10 ) NOT NULL, `sex` VARCHAR ( 2 ) NOT NULL, `password` VARCHAR ( 255 ) DEFAULT NULL, `username` VARCHAR ( 255 ) DEFAULT NULL, PRIMARY KEY ( `id` ) ) ENGINE = INNODB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8; INSERT INTO `test`.`user` ( `id`, `sex`, `password`, `username` ) VALUES ( 1, '男', '12344', '五六' ), ( 2, '女', '12643', '張三' ), ( 3, '男', '1245453', '李四' );
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 16:17 */ public class User { private Integer id; private String sex; private String password; private String username; // setter、getter略 }
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 16:17 */ public interface UserMapper { List<User> getUsers(); List<User> getUserBySexAndName(String sex, String username); int deleteUserById(Integer id); int updateUserByName(String username, String password); int insertUser(int id, String sex, String password, String username); }
<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="top.kk233.mapper.UserMapper"> <select id="getUsers" resultType="top.kk233.pojo.User"> SELECT * FROM user </select> <select id="getUserBySexAndName" resultType="top.kk233.pojo.User"> select * from user where sex=? and username=? </select> <delete id="deleteUserById" resultType="top.kk233.pojo.User"> delete from user where id=? </delete> <update id="updateUserByName" resultType="top.kk233.pojo.User"> update user set password=? where username=? </update> <insert id="insertUser" resultType="top.kk233.pojo.User"> insert into user values(?,?,?,?) </insert> </mapper>
<?xml version="1.0" encoding="UTF-8"?> <database> <property name="driverClassName">com.mysql.jdbc.Driver</property> <property name="url">jdbc:mysql://localhost:3306/test?useSSL=false</property> <property name="username">root</property> <property name="password">124760</property> </database>
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 16:24 */ public class app { public static void main(String[] args) { MySqlSession sql = new MySqlSession(); UserMapper mapper = sql.getMapper(UserMapper.class); List<User> users = mapper.getUsers(); users.forEach(System.out::println); System.out.println("=========================="); List<User> users1 = mapper.getUserBySexAndName("女", "張三"); users1.forEach(System.out::println); System.out.println("=========================="); mapper.deleteUserById(1); System.out.println("=========================="); mapper.updateUserByName("五六", "女"); System.out.println("=========================="); mapper.insertUser(10, "男", "123123", "五七"); } }
測試成功,這就是本人所手寫的Mybatis,雖然比較簡單,但還是學(xué)習到了很多東西。
項目放在 Gitee 上有需要自行下載,覺得可以還請點個Star
感謝各位的閱讀!關(guān)于“Mybatis怎么實現(xiàn)動態(tài)增刪改查功能”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。