目录
目录
概述
为什么要Mybatis,之前的jdbc不香吗??
JDBC的问题
问题解决思路
⾃定义框架设计
使用方:
提供核⼼配置⽂件:
下面是具体的实现:
框架方:
1.读取配置⽂件
实现方法:
首先编写一个Resources类,用于加载配置文件
2.解析配置文件:dom4j(解析xml)
第二步:封装两个javaBean
3.创建sqlSessionFactory
4.创建sqlSession接口及实现类:DefaultSqlSession
5.创建Executor接口及实现类 SimpleExecutor实现类
存在问题:
概述
为什么要Mybatis,之前的jdbc不香吗??
带着这个问题,我们看一下原生的jdbc是如何进行数据库交互的,下面是一个简单的查询语句。
- public static void main(String[] args) {
- Connection connection = null;
- PreparedStatement preparedStatement = null;
- ResultSet resultSet = null;
- try {
- // 加载数据库驱动
- Class.forName("com.mysql.jdbc.Driver");
- // 通过驱动管理类获取数据库链接
- connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis? characterEncoding = utf - 8 ", " root ", " root ");
- // 定义sql语句?表示占位符
- String sql = "select * from user where username = ?";
- // 获取预处理statement
- preparedStatement = connection.prepareStatement(sql);
- // 设置参数,第⼀个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值
- preparedStatement.setString(1, "tom");
- // 向数据库发出sql执⾏查询,查询出结果集
- resultSet = preparedStatement.executeQuery();
- // 遍历查询结果集
- while (resultSet.next()) {
- int id = resultSet.getInt("id");
- String username = resultSet.getString("username");
- // 封装User
- user.setId(id);
- user.setUsername(username);
- }
- System.out.println(user);
- }
- } catch(
- Exception e)
- {
- e.printStackTrace();
- } finally
- {
- // 释放资源
- if (resultSet != null) {
- try {
- resultSet.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- if (preparedStatement != null) {
- try {
- preparedStatement.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- if (connection != null) {
- try {
- connection.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- }
复制代码 what???一个简单的查询就做了这么多的操作,真实让人头疼。
JDBC的问题
下面我们再聊一聊其他问题JDBC问题:
原始jdbc开发存在的问题如下:
1、 数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能。(需要创建链接,三次握手等操作耗时)
2、 Sql语句在代码中硬编码,造成代码不易维护,实际应⽤中sql变化的可能较⼤,sql变动需要改变 java代码。
3、 使⽤preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不⼀定,可能 多也可能少,修改sql还要修改代码,系统不易维护。
4、 对结果集解析存在硬编码(查询列名,),手动设计结果集,如果结果很多,实现起来很麻烦,sql变化导致解析代码变化,系统不易维护,如果能将数据 库 记录封装成pojo对象解析⽐较⽅便
5、数据库配置信息 驱动等硬编码
带着上面的问题我们能不能优化一下,改吧jdbc的不足呢?
问题解决思路
提到硬编码--->配置文件解决
创建连接------>连接池
①使⽤数据库连接池初始化连接资源
②将sql语句抽取到xml配置⽂件中
③使⽤反射、内省等底层技术,⾃动将实体与表进⾏属性与字段的⾃动映射
根据上面的思路,我们来实现一个自己自定义的jdbc框架,实现对数据库的操作。
⾃定义框架设计
不可少的部分:
数据库配置信息、sql的配置信息(sql语句+参数类型+返回值类型) ------->由使用方进行提供
使用配置文件提供这些,具体见下面
使用方:
提供核⼼配置⽂件:
1)sqlMapConfig.xml : 存放数据源信息,引⼊mapper.xml
2)Mapper.xml : sql语句的配置⽂件信息
下面是具体的实现:
首先创建一个工程
我这里叫my_persistence_demo
主要进行了配置文件的编写
sqlM aMapConfig.xml内容如下:
主要是数据库的一些配置信息,个标签都是自定义的(可以根据喜好命名,下面的也是如此)
未来能够减少读取xml文件的次数,将userMapper.xml文件路径也引入了
- <configuration>
- <dataSource>
- <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
- <property name="jdbcUrl" value="jdbc:mysql:///custom_mybatis"></property>
- <property name="username" value="root"></property>
- <property name="password" value="root"></property>
- </dataSource>
-
- <mapper resource="userMapper.xml"></mapper>
- </configuration>
复制代码 我这里使用的是本地数据库 库名是custom_mybatis
建表语句如下:
- SET NAMES utf8mb4;
- SET FOREIGN_KEY_CHECKS = 0;
- -- ----------------------------
- -- Table structure for user
- -- ----------------------------
- DROP TABLE IF EXISTS `user`;
- CREATE TABLE `user` (
- `id` int(11) DEFAULT NULL,
- `name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
- SET FOREIGN_KEY_CHECKS = 1;
复制代码
userMapper.xml内容如下
只要是一些sql语句的配置
- <mapper namespace="user">
- <select id="selectList" resultType="cn.mystylefree.my_persistence_demo.domain.User">
- select * from user
- </select>
-
- <select id="selectOne" resultType="cn.mystylefree.my_persistence_demo.domain.User" paramterType="cn.mystylefree.my_persistence_demo.domain.User">
- select * from user where id= #{id} and name= #{name}
- </select>
复制代码 User实体类如下
- @Data
- public class User {
- private String id;
- private String name;
- }
复制代码 这是使用端的代码就基本写完了,下面是对服务端的编写
框架方:
其实就是对jdbc代码进行封装优化
项使用必须有上面的配置信息,将信息解析出来就能连接数据库了
- 读取配置⽂件
- 解析配置文件
- 创建sqlSessionFactory
- 创建sqlSession接口及实现类:主要封装crud操作
涉及到的设计模式:
Builder构建者设计模式、⼯⼚模式、代理模式
1.读取配置⽂件
创建Resources类。 方法。InputStream getResourceAsStream(String path)
读取完成以后以流的形式存在,
我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可以创建javaBean来存储
封装成两个实体 javaBean,存放的就是上面两个配置文件的信息
存放数据库基本信息存放的就是sqlMapConfig.xml配置信息的内容:
Map 唯⼀标识:namespace + "." + id
存Mapper.xml的配置
sql语句、statement类型、输⼊参数java类型、输出参数java类型
实现方法:
首先编写一个Resources类,用于加载配置文件
- public class Resources {
- /**
- * 根据指定的配置文件路径,将配置文件加载成字节输入流,存储到内参中
- * @param path
- * @return
- */
- public static InputStream getResourceAsStream(String path){
- InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
- return resourceAsStream;
- }
- }
复制代码 下面对这歌类进行测试
首先要将框架端端项目进行打包,这样使用方才能够引入该依赖
- <dependency>
- <groupId>cn.mystylefree</groupId>
- <artifactId>my_persistence</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- </dependency>
复制代码
说明一如成功了
创建测试类
2.解析配置文件:dom4j(解析xml)
创建sqlSessionFactoryBuilder类:
⽅法:sqlSessionFactory build(inputStream):
- 第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
- 第⼆:创建SqlSessionFactory的实现类DefaultSqlSession 产生sqlSession(会话对象)工厂模式
为啥不直接new 用工厂模式
第二步:封装两个javaBean
这里的MappedStatement的属性是根据 userMapper.xml中的配置标签来的
- @Data
- public class MappedStatement {
- //id标识
- private String id;
- //返回值类型
- private String resultType;
- //参数类型
- private String parameterType;
- //sql语句
- private String sql;
- }
复制代码 未了方便进行对象的传递,这里将这两个配置Bean封装成一个对象
DataSource就是数据库的配置信息
- @Data
- public class Configuration {
- private DataSource dataSource;
- /**
- * 将解析的Configuration 和MappedStatement同时封装到一起
- * 每一个sql语句就是一个MappedStatement
- * k 唯一标示 statementId namespace.id组成:这个唯一的id现在叫 statementId
- * v 分装好的MappedStatement对象
- */
- Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
- }
复制代码
使用dom4j解析配置,要引入相关的依赖
下面是框架端的全部依赖
- <dependencies>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>5.1.17</version>
- </dependency>
- <dependency>
- <groupId>c3p0</groupId>
- <artifactId>c3p0</artifactId>
- <version>0.9.1.2</version>
- </dependency>
- <dependency>
- <groupId>log4j</groupId>
- <artifactId>log4j</artifactId>
- <version>1.2.12</version>
- </dependency>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.10</version>
- </dependency>
- <dependency>
- <groupId>dom4j</groupId>
- <artifactId>dom4j</artifactId>
- <version>1.6.1</version>
- </dependency>
- <dependency>
- <groupId>jaxen</groupId>
- <artifactId>jaxen</artifactId>
- <version>1.1.6</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- </dependency>
- </dependencies>
复制代码- public class SqlSessionFactoryBuilder {
- /**
- * 创建sqlSessionFactoryBuilder类:
- * <p>
- * ⽅法:sqlSessionFactory build(inputStream):
- * <p>
- * 第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
- * 第⼆:创建SqlSessionFactory的实现类DefaultSqlSession 产生sqlSession(会话对象)工厂模式
- *
- * @param inputStream
- * @return
- */
- public SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException {
- //第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
- XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
- Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);
- //第⼆:创建SqlSessionFactory
- return null;
- }
- }
复制代码 通过parseConfig方法实现解析
对数据源信息解析
- public class XMLConfigBuilder {
- private Configuration configuration;
- /**
- * 在调用到时候就会执行无参构造
- */
- public XMLConfigBuilder() {
- this.configuration = new Configuration();
- }
- /**
- * 对将配置文件使用dom4j进行解析,封装Configuration的方法
- *
- * @param inputStream
- * @return
- */
- public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
- Document read = new SAXReader().read(inputStream);
- /*<configuration>根对象*/
- //数据源的解析
- Element rootElement = read.getRootElement();
- //表示 property 在任意位置都能获取到 一个property就是一个元素
- List<Element> list = rootElement.selectNodes("//property");
- //<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
- Properties properties = new Properties();
- for (Element element : list) {
- String name = element.attributeValue("name");
- String value = element.attributeValue("value");
- properties.setProperty(name, value);
- }
- ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
- comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
- comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
- comboPooledDataSource.setUser(properties.getProperty("username"));
- comboPooledDataSource.setPassword(properties.getProperty("password"));
- configuration.setDataSource(comboPooledDataSource);
- //mapper.xml解析
- /*@step1 获取路径 拿到路径 字节流 dom4j*/
- //<mapper resource="userMapper.xml"></mapper>
- List<Element> elements = rootElement.selectNodes("//mapper");
- for (Element element : elements) {
- String mapperPath = element.attributeValue("resource");
- InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);
- XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
- xmlMapperBuilder.parse(resourceAsStream);
- }
- return configuration;
- }
- }
复制代码 对sql配置xml的解析
- public class XMLMapperBuilder {
- private Configuration configuration;
- public XMLMapperBuilder(Configuration configuration) {
- this.configuration = configuration;
- }
- /**
- * <select id="selectList" resultType="cn.mystylefree.my_persistence_demo.domain.User">
- * select * from user
- * </select>
- *
- * @param inputStream
- * @throws DocumentException
- */
- public void parse(InputStream inputStream) throws DocumentException {
- Document read = new SAXReader().read(inputStream);
- //获取到了mapper节点 接下来获取select 标签
- Element rootElement = read.getRootElement();
- List<Element> selectNodes = rootElement.selectNodes("//select");
- /*没过element就是
- <select id="selectList" resultType="cn.mystylefree.my_persistence_demo.domain.User">
- select * from user
- </select>*/
- String namespace = rootElement.attributeValue("namespace");
- for (Element node : selectNodes) {
- String id = node.attributeValue("id");
- String resultType = node.attributeValue("resultType");
- String parameterType = node.attributeValue("parameterType");
- //sql语句 文本信息
- String sqlText = node.getTextTrim();
- MappedStatement mappedStatement = new MappedStatement();
- mappedStatement.setId(id);
- mappedStatement.setParameterType(parameterType);
- mappedStatement.setResultType(resultType);
- mappedStatement.setSql(sqlText);
- //唯一标识
- String key = namespace + "." + id;
- configuration.getMappedStatementMap().put(key, mappedStatement);
- }
- }
- }
复制代码 到此 Configuration配置就解析完成了
3.创建sqlSessionFactory
DefaultSqlSessionFactory 生产sqlSession
⽅法:openSession() : 获取sqlSession接⼝的实现类实例对象
- public class SqlSessionFactoryBuilder {
- /**
- * 创建sqlSessionFactoryBuilder类:
- * <p>
- * ⽅法:sqlSessionFactory build(inputStream):
- * <p>
- * 第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
- * 第⼆:创建SqlSessionFactory的实现类DefaultSqlSession 产生sqlSession(会话对象)工厂模式
- *
- * @param inputStream
- * @return
- */
- public SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException {
- //第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
- XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
- Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);
- /*
- * 第⼆:创建SqlSessionFactory
- * 是一个工厂类:生产sqlSession 会话对象
- * 数据库交互的增、删、改、查方法都封装在sqlSession对象
- * 以有参构造的方式将 配置传递下去
- * */
- SqlSessionFactory sqlSessionFactory=new DefaultSqlSessionFactory(configuration);
- return sqlSessionFactory;
- }
- }
复制代码- public interface SqlSessionFactory {
- public SqlSession openSession();
- }
复制代码- public class DefaultSqlSessionFactory implements SqlSessionFactory {
- private Configuration configuration;
- public DefaultSqlSessionFactory(Configuration configuration) {
- this.configuration = configuration;
- }
- @Override
- public SqlSession openSession() {
- return new DefaultSqlSession();
- }
- }
复制代码- public interface SqlSession {
- }
- public class DefaultSqlSession implements SqlSession {
- }
复制代码 此时创建sqlSessionFactory就完成了
4.创建sqlSession接口及实现类:DefaultSqlSession
主要封装crud操作
⽅法:
selectList(String statementId,Object param):查询所有
selectOne(String statementId,Object param):查询单个
update
delete
具体实现:封装JDBC完成对数据库表的查询操作
实现SqlSession接口
- public interface SqlSession {
- /**
- * 查询所有
- *
- * @param <E> 这里会有范型
- * user模块就是user Product就是product 所以不确定类型
- * @param statementId 这里是需要将查询的唯一标识传进来,
- * 只有传递这个id了才知道执行的sql语句(Configuration中查出来)
- * @param params 查询条件
- * @return
- */
- public <E> List<E> selectList(String statementId, Object... params);
- /**
- * 根据条件查询单个
- * @param statementId
- * @param params
- * @param <T>
- * @return
- */
- public <T> T selectOne(String statementId, Object... params);
- }
复制代码 实现
- 这里其实就能够直接写jdbc了,但是为了更优雅继续进行封装
复制代码- public class DefaultSqlSession implements SqlSession {
- /**
- * 这里其实就能够直接写jdbc了,但是为了更优雅继续进行封装
- * @param statementId 这里是需要将查询的唯一标识传进来,
- * 只有传递这个id了才知道执行的sql语句(Configuration中查出来)
- * @param params 查询条件
- * @param <E>
- * @return
- */
- @Override
- public <E> List<E> selectList(String statementId, Object... params) {
- return null;
- }
- @Override
- public <T> T selectOne(String statementId, Object... params) {
- List<Object> objects = this.selectList(statementId, params);
- if (objects.size() == 1) {
- return (T) objects.get(0);
- } else {
- throw new RuntimeException("查询结果为空或者查询结果过多");
-
- }
- }
- }
复制代码
5.创建Executor接口及实现类 SimpleExecutor实现类
query(Configuration, MappedStatement, Object ...params):执行的就是JDBC代码
参数信息就是步骤1中的对象信息
Object 操作数据库查询条件的具体参数值,不确定查询对象参数个数
如id=1 code=xsd
下面是用于解析的工具类
这是标记的解析器
- public class GenericTokenParser {
- private final String openToken; //开始标记
- private final String closeToken; //结束标记
- private final TokenHandler handler; //标记处理器
- public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
- this.openToken = openToken;
- this.closeToken = closeToken;
- this.handler = handler;
- }
- /**
- * 解析${}和#{}
- * @param text
- * @return
- * 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。
- * 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现
- */
- public String parse(String text) {
- // 验证参数问题,如果是null,就返回空字符串。
- if (text == null || text.isEmpty()) {
- return "";
- }
- // 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。
- int start = text.indexOf(openToken, 0);
- if (start == -1) {
- return text;
- }
- // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,
- // text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码
- char[] src = text.toCharArray();
- int offset = 0;
- final StringBuilder builder = new StringBuilder();
- StringBuilder expression = null;
- while (start > -1) {
- // 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
- if (start > 0 && src[start - 1] == '\\') {
- builder.append(src, offset, start - offset - 1).append(openToken);
- offset = start + openToken.length();
- } else {
- //重置expression变量,避免空指针或者老数据干扰。
- if (expression == null) {
- expression = new StringBuilder();
- } else {
- expression.setLength(0);
- }
- builder.append(src, offset, start - offset);
- offset = start + openToken.length();
- int end = text.indexOf(closeToken, offset);
- while (end > -1) {存在结束标记时
- if (end > offset && src[end - 1] == '\\') {//如果结束标记前面有转义字符时
- // this close token is escaped. remove the backslash and continue.
- expression.append(src, offset, end - offset - 1).append(closeToken);
- offset = end + closeToken.length();
- end = text.indexOf(closeToken, offset);
- } else {//不存在转义字符,即需要作为参数进行处理
- expression.append(src, offset, end - offset);
- offset = end + closeToken.length();
- break;
- }
- }
- if (end == -1) {
- // close token was not found.
- builder.append(src, start, src.length - start);
- offset = src.length;
- } else {
- //首先根据参数的key(即expression)进行参数处理,返回?作为占位符
- builder.append(handler.handleToken(expression.toString()));
- offset = end + closeToken.length();
- }
- }
- start = text.indexOf(openToken, offset);
- }
- if (offset < src.length) {
- builder.append(src, offset, src.length - offset);
- }
- return builder.toString();
- }
- }
复制代码 这里存的是context是参数名称 #{id} #{username} 中的id和username
就是#{}中的内容
- public class ParameterMapping {
- private String content;
- public ParameterMapping(String content) {
- this.content = content;
- }
- public String getContent() {
- return content;
- }
- public void setContent(String content) {
- this.content = content;
- }
- }
复制代码- public interface TokenHandler {
- String handleToken(String content);
- }
复制代码
- public class ParameterMappingTokenHandler implements TokenHandler {
- private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
- // context是参数名称 #{id} #{username} 中的id和username
- public String handleToken(String content) {
- parameterMappings.add(buildParameterMapping(content));
- return "?";
- }
- private ParameterMapping buildParameterMapping(String content) {
- ParameterMapping parameterMapping = new ParameterMapping(content);
- return parameterMapping;
- }
- public List<ParameterMapping> getParameterMappings() {
- return parameterMappings;
- }
- public void setParameterMappings(List<ParameterMapping> parameterMappings) {
- this.parameterMappings = parameterMappings;
- }
- }
复制代码
下面的是编写jdbc代码
在配置中有
- //step2 获取sql语句 select * from user where id= #{id} and name= #{name}
复制代码 mappedStatement中存储
- //id标识
- private String id;
- //返回值类型
- private String resultType;
- //参数类型
- private String parameterType;
- //sql语句
- private String sql;
复制代码 对sql进行解析 #{}的解析工作
- //step4 获取预处理对象 preparedStatement
复制代码
- ParameterMapping中存的是 #{}中的参数
复制代码
这是进行解析时调试的信息,将sql进行了替换,具体的替换方法在代码中有详细的标记,这里就不做过多的冗余介绍了。
数据库 数据如下:
这时就将数据库中的数据查到了,是不是感到很有成就感,给自己点赞
来源:https://blog.csdn.net/qq_29235677/article/details/125271197
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |