文章目录
中介者模式
中介者模式使用的场景: 把复杂功能的重复调用, 中间添加一层包装, 对外提供简单, 易扩展的服务能力. 例如中间件组提供的统一的接口与服务等.
中介者模式实战
模拟jdbc连接数据库的操作. 使用中介者模式,手写一个简易版的mybatis.
不使用中介者模式写法
创建模块tutorials-19.0-0 创建JDBCUtil类
该类是纯手写jdbc的过程.
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_ddd";
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 from user ");
// 如果有数据, rs.next()返回true
while (resultSet.next()) {
logger.info("测试结果 姓名: {}, 年龄: {}", resultSet.getString("name"), resultSet.getInt("age"));
}
}
}
使用中介者模式写法
使用中介者模式, 手写一个简易的mybatis查询框架.
uml图如下.
实现对配置文件的加载, xml的解析, 获取数据库的session, 数据库操作以及返回结果等.
定义SqlSession接口
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();
}
SqlSession具体实现类 DefaultSqlSession
public class DefaultSqlSession implements SqlSession{
// sql 连接
private Connection connection;
private Map<String, XNode> mapperElement;
public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) {
this.connection = connection;
this.mapperElement = mapperElement;
}
/**
* 无参数查询返回一个结果
* @param statement
* @param <T>
* @return
*/
@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> resultList = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
return resultList.get(0);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 有参数查询, 返回一个结果
* @param statement
* @param parameter
* @param <T>
* @return
*/
@Override
public <T> T selectOne(String statement, Object parameter) {
XNode xNode = mapperElement.get(statement);
Map<Integer, String> parameterMap = xNode.getParameter();
try {
PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
buildParameter(preparedStatement, parameter, parameterMap);
ResultSet resultSet = preparedStatement.executeQuery();
List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
return objects.get(0);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 解析查询参数
* @param preparedStatement
* @param parameter
* @param parameterMap
* @throws Exception
*/
private void buildParameter(PreparedStatement preparedStatement,
Object parameter,
Map<Integer, String> parameterMap) throws Exception {
int size = parameterMap.size();
// 判断参数类型
if (parameter instanceof Long) {
// the first parameter is 1, the second is 2 因此这里要从1开始.
for (int i = 1; i <= size; i++) {
preparedStatement.setLong(i, Long.parseLong(parameter.toString()));
}
return;
}
if (parameter instanceof Integer) {
for (int i = 1; i <=size; i++) {
preparedStatement.setInt(i, Integer.parseInt(parameter.toString()));
}
return;
}
if (parameter instanceof String) {
for (int i = 1; i <= size; i++) {
preparedStatement.setString(i, parameter.toString());
}
return;
}
// key 参数名 value: 字段的值.
HashMap<String, Object> fieldMap = new HashMap<>();
// 对象参数
Field[] declaredFields = parameter.getClass().getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
String name = field.getName();
Object obj = field.get(parameter);
fieldMap.put(name, obj);
}
for (int i = 1; i <=size; i++) {
String parameterDefine = parameterMap.get(i);
Object obj = fieldMap.get(parameterDefine);
if (obj instanceof Short) {
preparedStatement.setShort(i, Short.parseShort(obj.toString()));
continue;
}
if (obj instanceof Integer) {
preparedStatement.setInt(i, Integer.parseInt(obj.toString()));
continue;
}
if (obj instanceof Long) {
preparedStatement.setLong(i, Long.parseLong(obj.toString()));
continue;
}
if (obj instanceof String) {
preparedStatement.setString(i, obj.toString());
continue;
}
if (obj instanceof Date) {
// 如果是日期类型的, 转为sql的日期
preparedStatement.setDate(i, (java.sql.Date)obj);
}
}
}
/**
* 查询list集合 无参数
* @param statement
* @param <T>
* @return
*/
@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;
}
@Override
public <T> List<T> selectList(String statement, Object parameter) {
XNode xNode = mapperElement.get(statement);
Map<Integer, String> parameterMap = xNode.getParameter();
try {
PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
buildParameter(preparedStatement, parameter, parameterMap);
ResultSet resultSet = preparedStatement.executeQuery();
return resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 释放连接
*/
@Override
public void close() {
if (connection == null) {
return;
}
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* ResultSet 转化为对象集合
* @param resultSet
* @param clazz
* @param <T>
* @return
*/
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);
// 通过反射 set方法给对象设置值 例如id setId
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;
}
}
对查询的方法进行了实现, 包含sql的解析 ,结果的解析等.
定义SqlSessionFactory接口. 用于定义开启sqlSession
public interface SqlSessionFactory {
//开启一个SqlSession,有数据库操作的时候都会获取每一次执行的SqlSession
SqlSession openSession();
}
SqlSessionFactory具体实现类
开启SqlSession时会进行返回一个DefaultSqlSession
public class DefaultSqlSessionFactory implements SqlSessionFactory{
//配置文件类
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
// 开启SqlSession时会进行返回一个DefaultSqlSession
return new DefaultSqlSession(configuration.connection, configuration.mapperElement);
}
}
Configuration 类如下
public class Configuration {
protected Connection connection;
protected Map<String, String> dataSource;
protected Map<String, XNode> mapperElement;
public void setConnection(Connection connection) {
this.connection = connection;
}
public void setDataSource(Map<String, String> dataSource) {
this.dataSource = dataSource;
}
public void setMapperElement(Map<String, XNode> mapperElement) {
this.mapperElement = mapperElement;
}
}
Resources类
public class Resources {
public static Reader getResourceAsReader(String resource) throws Exception {
return new InputStreamReader(getResourceAsStream(resource));
}
private static InputStream getResourceAsStream(String resource) throws Exception {
ClassLoader[] classLoaders = getClassLoaders();
for (ClassLoader classLoader : classLoaders) {
InputStream inputStream = classLoader.getResourceAsStream(resource);
if (inputStream != null) {
return inputStream;
}
}
throw new IOException("could not find resource " + resource);
}
private static ClassLoader[] getClassLoaders() {
return new ClassLoader[]{
ClassLoader.getSystemClassLoader(),
Thread.currentThread().getContextClassLoader()
};
}
}
SqlSessionFactoryBuilder
public class SqlSessionFactoryBuilder {
// (构建实例化元素)
public DefaultSqlSessionFactory build(Reader reader) {
// 解析xml的sql 创建解析xml文件的类
SAXReader saxReader = new SAXReader();
// 这是为了保证在不联网的时候一样可以解析xml,否则会需要从互联网获取dtd文件。
saxReader.setEntityResolver(new XMLMapperEntityResolver());
try {
Document document = saxReader.read(new InputSource(reader));
Configuration configuration = parseConfiguration(document.getRootElement());
// 初始化SqlSession工厂类DefaultSqlSessionFactory
return new DefaultSqlSessionFactory(configuration);
} catch (DocumentException e) {
e.printStackTrace();
}
return null;
}
//(解析配置) 对xml中的元素进行获取
private Configuration parseConfiguration(Element root) {
Configuration configuration = new Configuration();
// 获取数据库连接信息
configuration.setDataSource(dataSource(root.selectNodes("//dataSource")));
//获取连接
configuration.setConnection(connection(configuration.dataSource));
//获取数据库操作 mapper.xml的sql语句信息
configuration.setMapperElement(mapperElement(root.selectNodes("mappers")));
return configuration;
}
// (解析sql语句)
private Map<String, XNode> mapperElement(List<Element> list) {
Map<String, XNode> map = new HashMap<>();
Element element = list.get(0);
List<Element> content = element.content();
for (Element e : content) {
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 selectNodes = root.selectNodes("select");
// 遍历所有的查询语句, 封装到map中
for (Object obj : selectNodes) {
Element node = (Element) obj;
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);
// 一个名称空间下, 所有的sql的集合映射
map.put(namespace + "." + id, xNode);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
return map;
}
/**
* 获取连接
* @param dataSource
* @return
*/
private Connection connection(Map<String, String> dataSource) {
try {
// 此处没有写死是加载MySQL还是oracle等数据库
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;
}
private Map<String, String> dataSource(List<Element> list) {
HashMap<String, String> dataSource = new HashMap<>();
Element element = list.get(0);
List<Element> content = element.content();
for (Element e : content) {
String name = e.attributeValue("name");
String value = e.attributeValue("value");
dataSource.put(name, value);
}
return dataSource;
}
}
实现sql语句解析, 解析数据库配置等.
测试验证
创建表
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');
创建用户类
@Data
public class User {
private Long id;
private String name;
private Integer age;
private Date createTime;
private Date updateTime;
}
创建学校类
@Data
public class School {
private Long id;
private String name;
private String address;
private Date createTime;
private Date updateTime;
}
创建 mybatis-config-datasource.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<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://192.168.....?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="........"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/User_Mapper.xml"/>
<mapper resource="mapper/School_Mapper.xml"/>
</mappers>
</configuration>
创建ISchoolDao
public interface ISchoolDao {
School querySchoolInfoById(Long treeId);
}
创建IUserDao
public interface IUserDao {
User queryUserInfoById(Long id);
List<User> queryUserList(User user);
}
创建School_Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.thc.design.dao.ISchoolDao">
<select id="querySchoolInfoById" resultType="com.thc.design.po.School">
SELECT id, name, address, createTime, updateTime
FROM school
where id = #{id}
</select>
</mapper>
创建User_Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.thc.design.dao.IUserDao">
<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="com.thc.design.po.User">
SELECT id, name, age, createTime, updateTime
FROM user
where id = #{id}
</select>
<select id="queryUserList" parameterType="com.thc.design.po.User" resultType="com.thc.design.po.User">
SELECT id, name, age, createTime, updateTime
FROM user
where age = #{age}
</select>
</mapper>
ApiTest 测试类
private Logger logger = LoggerFactory.getLogger(ApiTest.class);
// 单个结果查询测试
@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("com.thc.design.dao.IUserDao.queryUserInfoById", 1L);
logger.info("测试结果: {}", JSON.toJSONString(user));
} finally {
session.close();
reader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
集合查询
// 集合结果查询测试
@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("com.thc.design.dao.IUserDao.queryUserList", req);
logger.info("测试结果: {}", JSON.toJSONString(userList));
} finally {
session.close();
reader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}