做者:小傅哥
博客:https://bugstack.cn - 原創系列專題文章
html
沉澱、分享、成長,讓本身和他人都能有所收穫!😄
同齡人的差距是從何時拉開的
java
一樣的幼兒園、一樣的小學、同樣的書本、同樣的課堂,有人學習好、有人學習差。不僅是上學,幾乎人生到處都是賽道,發令槍響起的時刻,也就把人生的差距拉開。編程開發這條路也是很長很寬,有人跑得快有人跑得慢。那麼你是否想起過,這一點點的差距到高不可攀的距離,是從哪一天開始的。摸摸肚子的肉,看看遠處的路,別人講的是故事
,你想起的都是事故
。node
思想沒有產品高才寫出一片的ifelse
mysql
當你承接一個需求的時候,好比;交易、訂單、營銷、保險等各種場景。若是你不熟悉這個場景下的業務模式,以及未來的拓展方向,那麼很難設計出良好可擴展的系統。再加上產品功能初建,說老闆要的急,儘快上線。做爲程序員的你更沒有時間思考,總體一看如今的需求也不難,直接上手開幹(一個方法兩個if語句
),這樣確實知足了當前需求。但老闆的想法多呀,產品也跟着變化快,到你這就是改改改,加加加。固然你也不客氣,回首掏就是1024個if語句!程序員
日積月累的技術沉澱是爲了厚積薄發
正則表達式
粗略的估算過,若是從上大學開始天天寫200
行,一個月是6000
行,一年算10個月話,就是6萬行,第三年出去實習的是時候就有20
萬行的代碼量。若是你能作到這一點,找工做難?有時候不少事情就是靠時間積累出來的,想走捷徑有時候真的沒有。你的技術水平、你的業務能力、你身上的肉,都是一點點積累下來的,不要浪費看似很短的時間,一年年堅持下來,留下印刻青春的痕跡,多給本身武裝上一些能力。sql
bugstack蟲洞棧
,回覆源碼下載
獲取(打開獲取的連接,找到序號18)工程 | 描述 |
---|---|
itstack-demo-design-16-01 | 使用JDBC方式鏈接數據庫 |
itstack-demo-design-16-02 | 手寫ORM框架操做數據庫 |
中介者模式要解決的就是複雜功能應用之間的重複調用,在這中間添加一層中介者包裝服務,對外提供簡單、通用、易擴展的服務能力。數據庫
這樣的設計模式幾乎在咱們平常生活和實際業務開發中都會見到,例如;飛機🛬降落有小姐姐在塔臺喊話、不管哪一個方向來的候車都從站臺上下、公司的系統中有一箇中臺專門爲你包裝全部接口和提供統一的服務等等,這些都運用了中介者模式。除此以外,你用到的一些中間件,他們包裝了底層多種數據庫的差別化,提供很是簡單的方式進行使用。編程
在本案例中咱們經過模仿Mybatis手寫ORM框架,經過這樣操做數據庫學習中介者運用場景設計模式
除了這樣的中間件層使用場景外,對於一些外部接口,例如N種獎品服務,能夠由中臺系統進行統一包裝對外提供服務能力。也是中介者模式的一種思想體現。
在本案例中咱們會把jdbc層進行包裝,讓用戶在使用數據庫服務的時候,能夠和使用mybatis同樣簡單方便,經過這樣的源碼方式學習中介者模式,也方便對源碼知識的拓展學習,加強知識棧。
這是一種關於數據庫操做最初的方式
基本上每個學習開發的人都學習過直接使用jdbc方式鏈接數據庫,進行CRUD操做。如下的例子能夠當作回憶。
itstack-demo-design-16-01 └── src └── main └── java └── org.itstack.demo.design └── JDBCUtil.java
public class JDBCUtil { private static Logger logger = LoggerFactory.getLogger(JDBCUtil.class); public static final String URL = "jdbc:mysql://127.0.0.1:3306/itstack-demo-design"; public static final String USER = "root"; public static final String PASSWORD = "123456"; public static void main(String[] args) throws Exception { //1. 加載驅動程序 Class.forName("com.mysql.jdbc.Driver"); //2. 得到數據庫鏈接 Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); //3. 操做數據庫 Statement stmt = conn.createStatement(); ResultSet resultSet = stmt.executeQuery("SELECT id, name, age, createTime, updateTime FROM user"); //4. 若是有數據 resultSet.next() 返回true while (resultSet.next()) { logger.info("測試結果 姓名:{} 年齡:{}", resultSet.getString("name"),resultSet.getInt("age")); } } }
15:38:10.919 [main] INFO org.itstack.demo.design.JDBCUtil - 測試結果 姓名:水水 年齡:18 15:38:10.922 [main] INFO org.itstack.demo.design.JDBCUtil - 測試結果 姓名:豆豆 年齡:18 15:38:10.922 [main] INFO org.itstack.demo.design.JDBCUtil - 測試結果 姓名:花花 年齡:19 Process finished with exit code 0
`接下來就使用中介模式的思想完成模仿Mybatis的ORM框架開發~
itstack-demo-design-16-02 └── src ├── main │ ├── java │ │ └── org.itstack.demo.design │ │ ├── dao │ │ │ ├── ISchool.java │ │ │ └── IUserDao.java │ │ ├── mediator │ │ │ ├── Configuration.java │ │ │ ├── DefaultSqlSession.java │ │ │ ├── DefaultSqlSessionFactory.java │ │ │ ├── Resources.java │ │ │ ├── SqlSession.java │ │ │ ├── SqlSessionFactory.java │ │ │ ├── SqlSessionFactoryBuilder.java │ │ │ └── SqlSessionFactoryBuilder.java │ │ └── po │ │ ├── School.java │ │ └── User.java │ └── resources │ ├── mapper │ │ ├── School_Mapper.xml │ │ └── User_Mapper.xml │ └── mybatis-config-datasource.xml └── test └── java └── org.itstack.demo.design.test └── ApiTest.java
中介者模式模型結構
<T> T selectOne
、<T> List<T> selectList
等。DefaultSqlSession
SqlSessionFactoryBuilder
,這個類是對數據庫操做的核心類;處理工廠、解析文件、拿到session等。接下來咱們就分別介紹各個類的功能實現過程。
public interface SqlSession { <T> T selectOne(String statement); <T> T selectOne(String statement, Object parameter); <T> List<T> selectList(String statement); <T> List<T> selectList(String statement, Object parameter); void close(); }
public class DefaultSqlSession implements SqlSession { private Connection connection; private Map<String, XNode> mapperElement; public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) { this.connection = connection; this.mapperElement = mapperElement; } @Override public <T> T selectOne(String statement) { try { XNode xNode = mapperElement.get(statement); PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql()); ResultSet resultSet = preparedStatement.executeQuery(); List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType())); return objects.get(0); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public <T> List<T> selectList(String statement) { XNode xNode = mapperElement.get(statement); try { PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql()); ResultSet resultSet = preparedStatement.executeQuery(); return resultSet2Obj(resultSet, Class.forName(xNode.getResultType())); } catch (Exception e) { e.printStackTrace(); } return null; } // ... private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) { List<T> list = new ArrayList<>(); try { ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); // 每次遍歷行值 while (resultSet.next()) { T obj = (T) clazz.newInstance(); for (int i = 1; i <= columnCount; i++) { Object value = resultSet.getObject(i); String columnName = metaData.getColumnName(i); String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1); Method method; if (value instanceof Timestamp) { method = clazz.getMethod(setMethod, Date.class); } else { method = clazz.getMethod(setMethod, value.getClass()); } method.invoke(obj, value); } list.add(obj); } } catch (Exception e) { e.printStackTrace(); } return list; } @Override public void close() { if (null == connection) return; try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } }
public interface SqlSessionFactory { SqlSession openSession(); }
SqlSession
, 這幾乎是你們在平時的使用中都須要進行操做的內容。雖然你看不見,可是當你有數據庫操做的時候都會獲取每一次執行的SqlSession
。public class DefaultSqlSessionFactory implements SqlSessionFactory { private final Configuration configuration; public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } @Override public SqlSession openSession() { return new DefaultSqlSession(configuration.connection, configuration.mapperElement); } }
DefaultSqlSessionFactory
,是使用mybatis最經常使用的類,這裏咱們簡單的實現了一個版本。SqlSession
時會進行返回一個DefaultSqlSession
Configuration
配置文件,在這個配置文件中包括;Connection connection
、Map<String, String> dataSource
、Map<String, XNode> mapperElement
。若是有你閱讀過Mybatis源碼,對這個就不會陌生。public class SqlSessionFactoryBuilder { public DefaultSqlSessionFactory build(Reader reader) { SAXReader saxReader = new SAXReader(); try { saxReader.setEntityResolver(new XMLMapperEntityResolver()); Document document = saxReader.read(new InputSource(reader)); Configuration configuration = parseConfiguration(document.getRootElement()); return new DefaultSqlSessionFactory(configuration); } catch (DocumentException e) { e.printStackTrace(); } return null; } private Configuration parseConfiguration(Element root) { Configuration configuration = new Configuration(); configuration.setDataSource(dataSource(root.selectNodes("//dataSource"))); configuration.setConnection(connection(configuration.dataSource)); configuration.setMapperElement(mapperElement(root.selectNodes("mappers"))); return configuration; } // 獲取數據源配置信息 private Map<String, String> dataSource(List<Element> list) { Map<String, String> dataSource = new HashMap<>(4); Element element = list.get(0); List content = element.content(); for (Object o : content) { Element e = (Element) o; String name = e.attributeValue("name"); String value = e.attributeValue("value"); dataSource.put(name, value); } return dataSource; } private Connection connection(Map<String, String> dataSource) { try { Class.forName(dataSource.get("driver")); return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password")); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } return null; } // 獲取SQL語句信息 private Map<String, XNode> mapperElement(List<Element> list) { Map<String, XNode> map = new HashMap<>(); Element element = list.get(0); List content = element.content(); for (Object o : content) { Element e = (Element) o; String resource = e.attributeValue("resource"); try { Reader reader = Resources.getResourceAsReader(resource); SAXReader saxReader = new SAXReader(); Document document = saxReader.read(new InputSource(reader)); Element root = document.getRootElement(); //命名空間 String namespace = root.attributeValue("namespace"); // SELECT List<Element> selectNodes = root.selectNodes("select"); for (Element node : selectNodes) { String id = node.attributeValue("id"); String parameterType = node.attributeValue("parameterType"); String resultType = node.attributeValue("resultType"); String sql = node.getText(); // ? 匹配 Map<Integer, String> parameter = new HashMap<>(); Pattern pattern = Pattern.compile("(#\\{(.*?)})"); Matcher matcher = pattern.matcher(sql); for (int i = 1; matcher.find(); i++) { String g1 = matcher.group(1); String g2 = matcher.group(2); parameter.put(i, g2); sql = sql.replace(g1, "?"); } XNode xNode = new XNode(); xNode.setNamespace(namespace); xNode.setId(id); xNode.setParameterType(parameterType); xNode.setResultType(resultType); xNode.setSql(sql); xNode.setParameter(parameter); map.put(namespace + "." + id, xNode); } } catch (Exception ex) { ex.printStackTrace(); } } return map; } }
build(構建實例化元素)
、parseConfiguration(解析配置)
、dataSource(獲取數據庫配置)
、connection(Map<String, String> dataSource) (連接數據庫)
、mapperElement (解析sql語句)
build(構建實例化元素)
這個類主要用於建立解析xml文件的類,以及初始化SqlSession工廠類DefaultSqlSessionFactory
。另外須要注意這段代碼saxReader.setEntityResolver(new XMLMapperEntityResolver());
,是爲了保證在不聯網的時候同樣能夠解析xml,不然會須要從互聯網獲取dtd文件。
parseConfiguration(解析配置)
是對xml中的元素進行獲取,這裏主要獲取了;dataSource
、mappers
,而這兩個配置一個是咱們數據庫的連接信息,另一個是對數據庫操做語句的解析。
connection(Map<String, String> dataSource) (連接數據庫)
連接數據庫的地方和咱們常見的方式是同樣的;Class.forName(dataSource.get("driver"));
,可是這樣包裝之後外部是不須要知道具體的操做。同時當咱們須要連接多套數據庫的時候,也是能夠在這裏擴展。
mapperElement (解析sql語句)
這部分代碼塊內容相對來講比較長,可是核心的點就是爲了解析xml中的sql語句配置。在咱們日常的使用中基本都會配置一些sql語句,也有一些入參的佔位符。在這裏咱們使用正則表達式的方式進行解析操做。
解析完成的sql語句就有了一個名稱和sql的映射關係,當咱們進行數據庫操做的時候,這個組件就能夠經過映射關係獲取到對應sql語句進行操做。
在測試以前須要導入sql語句到數據庫中;
itstack-demo-design
user
、school
CREATE TABLE school ( id bigint NOT NULL AUTO_INCREMENT, name varchar(64), address varchar(256), createTime datetime, updateTime datetime, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into school (id, name, address, createTime, updateTime) values (1, '北京大學', '北京市海淀區頤和園路5號', '2019-10-18 13:35:57', '2019-10-18 13:35:57'); insert into school (id, name, address, createTime, updateTime) values (2, '南開大學', '中國天津市南開區衛津路94號', '2019-10-18 13:35:57', '2019-10-18 13:35:57'); insert into school (id, name, address, createTime, updateTime) values (3, '同濟大學', '上海市彰武路1號同濟大廈A樓7樓7區', '2019-10-18 13:35:57', '2019-10-18 13:35:57'); CREATE TABLE user ( id bigint(11) NOT NULL AUTO_INCREMENT, name varchar(32), age int(4), address varchar(128), entryTime datetime, remark varchar(64), createTime datetime, updateTime datetime, status int(4) DEFAULT '0', dateTime varchar(64), PRIMARY KEY (id), INDEX idx_name (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into user (id, name, age, address, entryTime, remark, createTime, updateTime, status, dateTime) values (1, '水水', 18, '吉林省榆樹市黑林鎮尹家村5組', '2019-12-22 00:00:00', '無', '2019-12-22 00:00:00', '2019-12-22 00:00:00', 0, '20200309'); insert into user (id, name, age, address, entryTime, remark, createTime, updateTime, status, dateTime) values (2, '豆豆', 18, '遼寧省大連市清河灣司馬道407路', '2019-12-22 00:00:00', '無', '2019-12-22 00:00:00', '2019-12-22 00:00:00', 1, null); insert into user (id, name, age, address, entryTime, remark, createTime, updateTime, status, dateTime) values (3, '花花', 19, '遼寧省大連市清河灣司馬道407路', '2019-12-22 00:00:00', '無', '2019-12-22 00:00:00', '2019-12-22 00:00:00', 0, '20200310');
用戶類
public class User { private Long id; private String name; private Integer age; private Date createTime; private Date updateTime; // ... get/set }
學校類
public class School { private Long id; private String name; private String address; private Date createTime; private Date updateTime; // ... get/set }
用戶Dao
public interface IUserDao { User queryUserInfoById(Long id); }
學校Dao
public interface ISchoolDao { School querySchoolInfoById(Long treeId); }
連接配置
<configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/itstack_demo_design?useUnicode=true"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mapper/User_Mapper.xml"/> <mapper resource="mapper/School_Mapper.xml"/> </mappers> </configuration>
操做配置(用戶)
<mapper namespace="org.itstack.demo.design.dao.IUserDao"> <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="org.itstack.demo.design.po.User"> SELECT id, name, age, createTime, updateTime FROM user where id = #{id} </select> <select id="queryUserList" parameterType="org.itstack.demo.design.po.User" resultType="org.itstack.demo.design.po.User"> SELECT id, name, age, createTime, updateTime FROM user where age = #{age} </select> </mapper>
操做配置(學校)
<mapper namespace="org.itstack.demo.design.dao.ISchoolDao"> <select id="querySchoolInfoById" resultType="org.itstack.demo.design.po.School"> SELECT id, name, address, createTime, updateTime FROM school where id = #{id} </select> </mapper>
@Test public void test_queryUserInfoById() { String resource = "mybatis-config-datasource.xml"; Reader reader; try { reader = Resources.getResourceAsReader(resource); SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader); SqlSession session = sqlMapper.openSession(); try { User user = session.selectOne("org.itstack.demo.design.dao.IUserDao.queryUserInfoById", 1L); logger.info("測試結果:{}", JSON.toJSONString(user)); } finally { session.close(); reader.close(); } } catch (Exception e) { e.printStackTrace(); } }
Mybatis
是同樣的,都包括了;資源加載和解析、SqlSession
工廠構建、開啓SqlSession
以及最後執行查詢操做selectOne
測試結果
16:56:51.831 [main] INFO org.itstack.demo.design.demo.ApiTest - 測試結果:{"age":18,"createTime":1576944000000,"id":1,"name":"水水","updateTime":1576944000000} Process finished with exit code 0
@Test public void test_queryUserList() { String resource = "mybatis-config-datasource.xml"; Reader reader; try { reader = Resources.getResourceAsReader(resource); SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader); SqlSession session = sqlMapper.openSession(); try { User req = new User(); req.setAge(18); List<User> userList = session.selectList("org.itstack.demo.design.dao.IUserDao.queryUserList", req); logger.info("測試結果:{}", JSON.toJSONString(userList)); } finally { session.close(); reader.close(); } } catch (Exception e) { e.printStackTrace(); } }
session.selectList
,是查詢一個集合結果。測試結果
16:58:13.963 [main] INFO org.itstack.demo.design.demo.ApiTest - 測試結果:[{"age":18,"createTime":1576944000000,"id":1,"name":"水水","updateTime":1576944000000},{"age":18,"createTime":1576944000000,"id":2,"name":"豆豆","updateTime":1576944000000}] Process finished with exit code 0
Mybatis
的原型,在咱們平常的開發使用中,只須要按照配置便可很是簡單的操做數據庫。單一職責
和開閉原則
,也就符合了迪米特原則
,即越少人知道越好。外部的人只須要按照需求進行調用,不須要知道具體的是如何實現的,複雜的一面已經有組件合做服務平臺處理。