1、自定义持久层框架
1.1、分析JDBC操作问题
package blnp.net.cn.jvm.demos;import java.sql.*;/*** <p></p>** @author lyb 2045165565@qq.com* @createDate 2024/5/24 14:24*/
public class JdbcTest {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/my_test?characterEncoding=utf-8", "root", "root");// 定义sql语句?表示占位符String sql = "select * from heros where name = ?";// 获取预处理statementpreparedStatement = connection.prepareStatement(sql);// 设置参数,第 1⼀个参数为sql语句中参数的序号(从1开始),第⼆1 个参数为设置的参数值preparedStatement.setString(1, "张飞");// 向数据库发出sql执⾏查询,查询出结果集resultSet = preparedStatement.executeQuery();// 遍历查询结果集while (resultSet.next()) {int id = resultSet.getInt("id");String hpMax = resultSet.getString("hp_max");// 封装 Hero 对象//user.setId(id);//user.setUsername(username);System.out.println("hpMax = " + hpMax);}} 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();}}}}
}
JDBC问题总结:
使用原始JDBC开发会存在以下这些问题:
- 数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能。
- Sql语句在代码中硬编码,造成代码不易维护,实际应⽤中sql变化的可能较⼤,sql变动需要改变java代码。
- 使⽤ preparedStatement 向占有位符号传参数存在硬编码,因为sql语句的 where 条件不⼀定,可能多也可能少,修改sql还要修改代码,系统不易维护。
- 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析⽐较⽅便
1.2、问题解决的思路
- 使⽤数据库连接池初始化连接资源
- 将sql语句抽取到xml配置⽂件中
- 使⽤反射、内省等底层技术,⾃动将实体与表进⾏属性与字段的⾃动映射
1.3、自定义框架设计步骤
1.3.1、使用端
提供核心配置文件:
- sqlMapConfig.xml : 存放数据源信息
- 引⼊mapper.xml
- Mapper.xml : sql语句的配置⽂件信息
1.3.2、框架端
1、读取配置文件
读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可以创建 javaBean 来存储。
- Configuration : 存放数据库基本信息、Map<唯⼀标识,Mapper> 唯⼀标识:namespace + "." + id
- MappedStatement:sql语句、statement类型、输⼊参数java类型、输出参数java类型
2、解析配置文件
创建sqlSessionFactoryBuilder类,使用⽅法:sqlSessionFactory build()。
- 使⽤dom4j解析配置⽂件,将解析出来的内容封装到Configuration和MappedStatement中
- 创建SqlSessionFactory的实现类DefaultSqlSession
3、创建 SqlSessionFactory
通过⽅法:openSession() : 获取sqlSession接⼝的实现类实例对象
4、创建sqlSession接⼝及实现类:主要封装crud⽅法
//查询所有
selectList(String statementId,Object param)//查询单个
selectOne(String statementId,Object param)
1.4、自定义框架实现
1.4.1、使用端配置
首先创建一个Maven工程,使用JDK8.引用的依赖如下文件所示。最终使用端创建的相关文件结构可以见下图:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.blnp.net</groupId><artifactId>mybatisPersistence</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.encoding>UTF-8</maven.compiler.encoding><java.version>1.8</java.version><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><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></dependencies></project>
接着创建 sqlMapConfig.xml 文件,保存关于数据库的相关链接信息以及Mapper映射文件的路径信息。
<configuration><!--数据库连接信息--><property name="driverClass" value="com.mysql.jdbc.Driver"></property><property name="jdbcUrl" value="jdbc:mysql:///my_test"></property><property name="user" value="root"></property><property name="password" value="admin@123"></property><!--引入sql配置信息 --><mapper resource="HerosMapper.xml"></mapper>
</configuration>
接着创建我们测试使用的数据库表对象实体 HerosEntity:
package com.blnp.net.entity;import java.io.Serializable;
import java.util.Date;public class HerosEntity implements Serializable {private Integer id;private String name;private Double hpMax;private Double hpGrowth;private Double hpStart;private Double mpMax;private Double mpGrowth;private Double mpStart;private Double attackMax;private Double attackGrowth;private Double attackStart;private Double defenseMax;private Double defenseGrowth;private Double defenseStart;private Double hp5sMax;private Double hp5sGrowth;private Double hp5sStart;private Double mp5sMax;private Double mp5sGrowth;private Double mp5sStart;private Double attackSpeedMax;private String attackRange;private String roleMain;private String roleAssist;private Date birthdate;private static final long serialVersionUID = 1L;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name == null ? null : name.trim();}public Double getHpMax() {return hpMax;}public void setHpMax(Double hpMax) {this.hpMax = hpMax;}public Double getHpGrowth() {return hpGrowth;}public void setHpGrowth(Double hpGrowth) {this.hpGrowth = hpGrowth;}public Double getHpStart() {return hpStart;}public void setHpStart(Double hpStart) {this.hpStart = hpStart;}public Double getMpMax() {return mpMax;}public void setMpMax(Double mpMax) {this.mpMax = mpMax;}public Double getMpGrowth() {return mpGrowth;}public void setMpGrowth(Double mpGrowth) {this.mpGrowth = mpGrowth;}public Double getMpStart() {return mpStart;}public void setMpStart(Double mpStart) {this.mpStart = mpStart;}public Double getAttackMax() {return attackMax;}public void setAttackMax(Double attackMax) {this.attackMax = attackMax;}public Double getAttackGrowth() {return attackGrowth;}public void setAttackGrowth(Double attackGrowth) {this.attackGrowth = attackGrowth;}public Double getAttackStart() {return attackStart;}public void setAttackStart(Double attackStart) {this.attackStart = attackStart;}public Double getDefenseMax() {return defenseMax;}public void setDefenseMax(Double defenseMax) {this.defenseMax = defenseMax;}public Double getDefenseGrowth() {return defenseGrowth;}public void setDefenseGrowth(Double defenseGrowth) {this.defenseGrowth = defenseGrowth;}public Double getDefenseStart() {return defenseStart;}public void setDefenseStart(Double defenseStart) {this.defenseStart = defenseStart;}public Double getHp5sMax() {return hp5sMax;}public void setHp5sMax(Double hp5sMax) {this.hp5sMax = hp5sMax;}public Double getHp5sGrowth() {return hp5sGrowth;}public void setHp5sGrowth(Double hp5sGrowth) {this.hp5sGrowth = hp5sGrowth;}public Double getHp5sStart() {return hp5sStart;}public void setHp5sStart(Double hp5sStart) {this.hp5sStart = hp5sStart;}public Double getMp5sMax() {return mp5sMax;}public void setMp5sMax(Double mp5sMax) {this.mp5sMax = mp5sMax;}public Double getMp5sGrowth() {return mp5sGrowth;}public void setMp5sGrowth(Double mp5sGrowth) {this.mp5sGrowth = mp5sGrowth;}public Double getMp5sStart() {return mp5sStart;}public void setMp5sStart(Double mp5sStart) {this.mp5sStart = mp5sStart;}public Double getAttackSpeedMax() {return attackSpeedMax;}public void setAttackSpeedMax(Double attackSpeedMax) {this.attackSpeedMax = attackSpeedMax;}public String getAttackRange() {return attackRange;}public void setAttackRange(String attackRange) {this.attackRange = attackRange == null ? null : attackRange.trim();}public String getRoleMain() {return roleMain;}public void setRoleMain(String roleMain) {this.roleMain = roleMain == null ? null : roleMain.trim();}public String getRoleAssist() {return roleAssist;}public void setRoleAssist(String roleAssist) {this.roleAssist = roleAssist == null ? null : roleAssist.trim();}public Date getBirthdate() {return birthdate;}public void setBirthdate(Date birthdate) {this.birthdate = birthdate;}@Overridepublic String toString() {StringBuilder sb = new StringBuilder();sb.append(getClass().getSimpleName());sb.append(" [");sb.append("Hash = ").append(hashCode());sb.append(", id=").append(id);sb.append(", name=").append(name);sb.append(", hpMax=").append(hpMax);sb.append(", hpGrowth=").append(hpGrowth);sb.append(", hpStart=").append(hpStart);sb.append(", mpMax=").append(mpMax);sb.append(", mpGrowth=").append(mpGrowth);sb.append(", mpStart=").append(mpStart);sb.append(", attackMax=").append(attackMax);sb.append(", attackGrowth=").append(attackGrowth);sb.append(", attackStart=").append(attackStart);sb.append(", defenseMax=").append(defenseMax);sb.append(", defenseGrowth=").append(defenseGrowth);sb.append(", defenseStart=").append(defenseStart);sb.append(", hp5sMax=").append(hp5sMax);sb.append(", hp5sGrowth=").append(hp5sGrowth);sb.append(", hp5sStart=").append(hp5sStart);sb.append(", mp5sMax=").append(mp5sMax);sb.append(", mp5sGrowth=").append(mp5sGrowth);sb.append(", mp5sStart=").append(mp5sStart);sb.append(", attackSpeedMax=").append(attackSpeedMax);sb.append(", attackRange=").append(attackRange);sb.append(", roleMain=").append(roleMain);sb.append(", roleAssist=").append(roleAssist);sb.append(", birthdate=").append(birthdate);sb.append("]");return sb.toString();}
}
最后,创建我们实体映射文件以及使用的SQL语句 HerosMapper.xml :
<mapper namespace="Heros"><!--SQL的唯一标识:由 namespace.id 加上 statementId 来组成并定位到语句--><!--查询所有--><select id="selectAll" resultType="com.blnp.net.entity.HerosEntity">select * from heros</select><!--查询单个--><select id="selectOne" paramterType="com.blnp.net.entity.HerosEntity" resultType="com.blnp.net.entity.HerosEntity">select * from heros where id=#{id} and name=#{name}</select>
</mapper>
1.4.2、框架端配置
1、准备工作 & 加载配置文件
这边我们新建一个空白的Maven工程,并创建一个 Resources 类。完整代码如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.blnp.net</groupId><artifactId>persistenceFrame</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties></project>
package com.blnp.net.io;import java.io.InputStream;/*** <p>框架资源加载类</p>** @author lyb 2045165565@qq.com* @createDate 2024/5/24 16:51*/
public class Resources {/*** 用途:指定文件路径获取字节流* 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中* @author liaoyibin* @date 16:54 2024/5/24* @params [path]* @param path**/public static InputStream getResourceAsStream(String path) {//跟进当前类的类加载器进行获取InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);return resourceAsStream;}
}
随后将该工程进行打包输出:
接着,继续在我们之前的使用端工程里引入该自定义持久框架工程的包。并编写如下测试代码:
2、创建容器对象
创建 MappedStatement 容器对象
package com.blnp.net.pojo;/*** <p>MappedStatement容器对象</p>** @author lyb 2045165565@qq.com* @createDate 2024/5/24 17:17*/
public class MappedStatement {/*** id标识**/private String id;/*** 返回值类型**/private Class<?> resultType;/*** 参数值类型**/private Class<?> paramType;/*** sql语句**/private String sql;public String getId() {return id;}public void setId(String id) {this.id = id;}public Class<?> getResultType() {return resultType;}public void setResultType(Class<?> resultType) {this.resultType = resultType;}public Class<?> getParamType() {return paramType;}public void setParamType(Class<?> paramType) {this.paramType = paramType;}public String getSql() {return sql;}public void setSql(String sql) {this.sql = sql;}
}
创建数据库连接信息容器对象
package com.blnp.net.pojo;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** <p>数据库连接信息容器对象</p>** @author lyb 2045165565@qq.com* @createDate 2024/5/24 17:25*/
public class Configuration {/*** 数据库连接配置信息**/private DataSource dataSource;/*** mapper文件中的查询语句信息**/private Map<String,MappedStatement> mappedStatementMap = new HashMap<>();public DataSource getDataSource() {return dataSource;}public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}public Map<String, MappedStatement> getMappedStatementMap() {return mappedStatementMap;}public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {this.mappedStatementMap = mappedStatementMap;}
}
3、解析核心配置文件 sqlMapConfig
1、首先创建以下接口类:
package com.blnp.net.sqlSession;/*** <p>数据库连接会话接口</p>** @author lyb 2045165565@qq.com* @createDate 2024/5/24 17:36*/
public interface SqlSessionFactory {}
2、接着创建文件解析类
package com.blnp.net.config;import com.blnp.net.io.Resources;
import com.blnp.net.pojo.Configuration;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;/*** <p>文件解析助手</p>** @author lyb 2045165565@qq.com* @createDate 2024/5/24 17:45*/
public class XmlConfigBuilder {/*** 自定义配置**/private Configuration configuration;public XmlConfigBuilder(Configuration configuration) {this.configuration = configuration;}/*** 用途:sqlMapConfig.xml 转为 javabean 对象* @author liaoyibin* @date 18:00 2024/5/24* @params [inputStream]* @param inputStream**/public Configuration parseConfiguration(InputStream inputStream) throws DocumentException,PropertyVetoException,ClassNotFoundException {//将字节流解析为文档对象Document document = new SAXReader().read(inputStream);//获取根元素对象Element rootElement = document.getRootElement();//获取文档中根节点内所有的 property 标签对象List<Element> propertyElements = rootElement.selectNodes("//property");Properties properties = new Properties();//迭代获取数据for (Element propertyElement : propertyElements) {String name = propertyElement.attributeValue("name");String value = propertyElement.attributeValue("value");properties.setProperty(name,value);}/*** 连接池信息解析读取**/ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();//获取驱动类信息comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));//获取JDBC连接地址comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));//获取数据库连接用户密码comboPooledDataSource.setUser(properties.getProperty("username"));comboPooledDataSource.setPassword(properties.getProperty("password"));//填充configurationconfiguration.setDataSource(comboPooledDataSource);/*** mapper部分信息解析读取**/List<Element> mapperElements = rootElement.selectNodes("//mapper");XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration);//迭代解析mapperfor (Element mapperElement : mapperElements) {//mapper文件路径String mapperPath = mapperElement.attributeValue("resource");//获取当前mapper的字节流对象InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);//解析对应对象xmlMapperBuilder.parse(resourceAsStream);}return configuration;}
}
4、解析映射配置文件mapper
package com.blnp.net.config;import com.blnp.net.pojo.Configuration;
import com.blnp.net.pojo.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import java.io.InputStream;
import java.util.List;
import java.util.Optional;/*** <p>xml mapper映射文件解析读取</p>** @author lyb 2045165565@qq.com* @createDate 2024/5/24 18:00*/
public class XmlMapperBuilder {private Configuration configuration;public XmlMapperBuilder(Configuration configuration) {this.configuration = configuration;}/*** 用途:Mapper映射文件解析读取* @author liaoyibin* @date 18:13 2024/5/24* @params [inputStream]* @param inputStream**/public void parse(InputStream inputStream) throws DocumentException,ClassNotFoundException {//流对象解析处理Document document = new SAXReader().read(inputStream);//获取根节点对象Element rootElement = document.getRootElement();//获取 Mapper 文件的命名空间String namespace = rootElement.attributeValue("namespace");//获取 Mapper 文件的所有查询语句的 StatementList<Element> select = rootElement.selectNodes("//select");//迭代解析处理for (Element element : select) {//即查询语句的IDString id = element.attributeValue("id");//请求参数String paramType = element.attributeValue("paramType");//响应参数String resultType = element.attributeValue("resultType");//反射获取入参对象Class<?> paramTypeClass = getClassType(paramType);//返回结果classClass<?> resultTypeClass = getClassType(resultType);//statementIdString key = namespace + "." + id;//sql语句String textTrim = element.getTextTrim();/*** 封装 mappedStatement**/MappedStatement mappedStatement = new MappedStatement();mappedStatement.setId(id);mappedStatement.setResultType(resultTypeClass);mappedStatement.setParamType(paramTypeClass);mappedStatement.setSql(textTrim);//填充 configurationconfiguration.getMappedStatementMap().put(key, mappedStatement);}}/*** 用途:根据类全限定名进行反射获取对象实例* @author liaoyibin* @date 18:10 2024/5/24* @params [paramType]* @param paramType**/private Class<?> getClassType (String paramType) throws ClassNotFoundException {if (!Optional.ofNullable(paramType).isPresent()) {return null;}Class<?> aClass = Class.forName(paramType);return aClass;}
}
5、会话对象sqlSession处理
package com.blnp.net.sqlSession;import com.blnp.net.config.XmlConfigBuilder;
import com.blnp.net.pojo.Configuration;
import com.blnp.net.sqlSession.defaults.DefaultSqlSessionFactory;
import org.dom4j.DocumentException;import java.beans.PropertyVetoException;
import java.io.InputStream;/*** <p>sqlMapConfig.xml 文件解析类</p>** @author lyb 2045165565@qq.com* @createDate 2024/5/24 17:40*/
public class SqlSessionFactoryBuilder {private Configuration configuration;public SqlSessionFactoryBuilder() {this.configuration = new Configuration();}public SqlSessionFactory build(InputStream inputStream) throws DocumentException,PropertyVetoException,ClassNotFoundException {/*** 第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中**///1.解析配置⽂件,封装ConfigurationXmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(configuration);//解析处理Configuration configuration = xmlConfigBuilder.parseConfiguration(inputStream);/*** 第二:创建sqlSessionFactory对象:工厂类:生产sqlSession:会话对象**/SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);return sqlSessionFactory;}
}
6、解析实现与结果封装
package com.blnp.net.sqlSession.defaults;import com.blnp.net.config.BoundSql;
import com.blnp.net.pojo.Configuration;
import com.blnp.net.pojo.MappedStatement;
import com.blnp.net.pojo.ParameterMapping;
import com.blnp.net.sqlSession.Executor;
import com.blnp.net.utils.ColumnUtil;
import com.blnp.net.utils.GenericTokenParser;import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;/*** <p>mapper代理对象生成实现</p>** @author lyb 2045165565@qq.com* @createDate 2024/5/27 9:33*/
public class SimpleExecutor implements Executor {/*** 数据库连接**/private Connection connection = null;@Overridepublic <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception {// 1.注册驱动,获取连接connection = configuration.getDataSource().getConnection();// 2.获取sql语句 eg: select * from user where id = #{id} and username = #{username}//转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储String sql = mappedStatement.getSql();//对SQL进行处理BoundSql boundsql = getBoundSql(sql);//获取处理后的SQLString finalSql = boundsql.getSqlText();//获取传⼊参数类型Class<?> paramType = mappedStatement.getParamType();//3.获取预编译preparedStatement对象PreparedStatement preparedStatement = connection.prepareStatement(finalSql);//获取传入参数列表List<ParameterMapping> parameterMappingList = boundsql.getParameterMappingList();//4.设置参数for (int i = 0; i < parameterMappingList.size(); i++) {ParameterMapping parameterMapping = parameterMappingList.get(i);//获取SQL内执行条件字段的名称String name = parameterMapping.getContent();//反射获取对应字段信息Field declaredField = paramType.getDeclaredField(name);//防止字段是私有的,开启暴力访问declaredField.setAccessible(true);//参数的值Object o = declaredField.get(params[0]);//给占位符赋值preparedStatement.setObject(i + 1, o);}//5.执行结果解析处理ResultSet resultSet = preparedStatement.executeQuery();//取得待执行SQL语句的输出响应参数定义信息Class<?> resultType = mappedStatement.getResultType();ArrayList<Object> results = new ArrayList<Object>();while (resultSet.next()) {//获取元数据ResultSetMetaData metaData = resultSet.getMetaData();Object o = resultType.newInstance();//总列数int columnCount = metaData.getColumnCount();for (int i = 1; i <= columnCount; i++) {//属性名String columnName = metaData.getColumnName(i);//属性值Object value = resultSet.getObject(columnName);//对属性名进行驼峰转换String fieldName = ColumnUtil.lineToHump(columnName);//创建属性描述器,为属性⽣成读写⽅法PropertyDescriptor propertyDescriptor = new PropertyDescriptor(fieldName, resultType);//获取写方法Method writeMethod = propertyDescriptor.getWriteMethod();//获取设置值的参数类型String paramTypeName = writeMethod.getParameterTypes()[0].getName();//根据参数类型进行转换处理Object paramVal = switchParam(paramTypeName, value);//向类中写⼊值writeMethod.invoke(o, paramVal);}results.add(o);}return (List<E>) results;}/*** 用途:SQL解析标记* @author liaoyibin* @date 9:55 2024/5/27* @params [sql]* @param sql**/private BoundSql getBoundSql(String sql) {//标记处理类:主要是配合通⽤标记解析器GenericTokenParser类完成对配置⽂件等的解析⼯作,其中TokenHandler主要完成处理ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();//GenericTokenParser :通⽤的标记解析器,完成了代码⽚段中的占位符的解析,然后再根据给定的标记处理器(TokenHandler)来进⾏表达式的处理//三个参数:分别为openToken (开始标记)、closeToken (结束标记)、handler (标记处理器)GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);String parse = genericTokenParser.parse(sql);List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();BoundSql boundSql = new BoundSql(parse, parameterMappings);return boundSql;}/*** 用途:参数类型转换* @author liaoyibin* @date 19:16 2024/5/27* @params [paramName, val]* @param paramName* @param val**/private Object switchParam(String paramName,Object val) {if (!Optional.ofNullable(paramName).isPresent()) {return null;}if (Objects.equals(paramName,Integer.class.getName())) {return Integer.valueOf(val+"");}else if (Objects.equals(paramName,Double.class.getName())) {return Double.valueOf(val+"");}else if (Objects.equals(paramName, String.class.getName())) {return String.valueOf(val);}else if (Objects.equals(paramName, Date.class.getName())) {return new Date();}else {return null;}}@Overridepublic void close() throws SQLException {connection.close();}
}
7、集成自定义框架测试
package com.blnp.net.demo;import com.blnp.net.entity.HerosEntity;
import com.blnp.net.io.Resources;
import com.blnp.net.sqlSession.SqlSession;
import com.blnp.net.sqlSession.SqlSessionFactory;
import com.blnp.net.sqlSession.SqlSessionFactoryBuilder;
import org.dom4j.DocumentException;
import org.junit.Test;import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;/*** <p></p>** @author lyb 2045165565@qq.com* @createDate 2024/5/24 16:57*/
public class IPersistenceTest {public static void main(String[] args) throws Exception {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory = factoryBuilder.build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();HerosEntity hero = new HerosEntity();hero.setId(10005);hero.setName("亚瑟");HerosEntity result = sqlSession.selectOne("Heros.selectOne", hero);System.out.println("result = " + result.toString());}@Testpublic void selectAll() throws Exception {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory = factoryBuilder.build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();List<HerosEntity> objects = sqlSession.selectList("Heros.selectAll");for (HerosEntity object : objects) {System.out.println("object = " + object.toString());}}
}
查询所有方法:
8、源码下载
1、自定义持久层框架源码:persistenceFrame.7z - 蓝奏云
2、测试程序:mybatisPersistence.7z - 蓝奏云
1.4.3、常见问题
- 比如数据库查询返回的字段名,驼峰问题或下划线问题
- 比如实体类字段参数类型问题
- 其它问题
1.5、自定义框架优化
通过自定义持久层框架后,我们解决了JDBC操作数据库带来的⼀些问题:例如频繁创建释放数据库连接,硬编码,⼿动封装返回结果集等问题。但是我们自定义的框架同样也带来了一些问题:
- dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调⽤sqlsession⽅法,关闭 sqlsession)
- dao的实现类中存在硬编码,调⽤sqlsession的⽅法时,参数statement的id硬编码
解决方法:使⽤代理模式来创建接⼝的代理对象在sqlSession中添加⽅法实
@Test
public void test2() throws Exception {InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml")SqlSessionFactory build = newSqlSessionFactoryBuilder().build(resourceAsSteam);SqlSession sqlSession = build.openSession();User user = new User();user.setld(l);user.setUsername("tom");//代理对象UserMapper userMapper = sqlSession.getMappper(UserMapper.class);User userl = userMapper.selectOne(user);System.out.println(userl);
}
在sqlSession中添加⽅法:
public interface SqlSession {public <T> T getMappper(Class<?> mapperClass);}
对应实现类:
@Overridepublic <T> T getMapper(Class<?> mapperClass) {// 使用JDK动态代理来为Dao接口生成代理对象,并返回Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne// 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名// 方法名:findAllString methodName = method.getName();String className = method.getDeclaringClass().getName();String statementId = className+"."+methodName;// 准备参数2:params:args// 获取被调用方法的返回值类型Type genericReturnType = method.getGenericReturnType();// 判断是否进行了 泛型类型参数化if(genericReturnType instanceof ParameterizedType){List<Object> objects = selectList(statementId, args);return objects;}return selectOne(statementId,args);}});return (T) proxyInstance;}
1.6、 修改删除补充
1.6.1、编码步骤
- 修改XMLMapperBuilder的parse方法:解析<update><delete>标签
- 在SqlSession及DefalutSqlSession添加update及delete方法
- 在getMapper方法中对当前执行的sql语句进行判断,决定调用增删改查的那个方法
- 在Executor及simpleExecutor中添加update及delete方法
1.6.2、代码示例
XMLMapperBuilder:
public class XMLMapperBuilder {private Configuration configuration;public XMLMapperBuilder(Configuration configuration) {this.configuration =configuration;}public void parse(InputStream inputStream) throws DocumentException {Document document = new SAXReader().read(inputStream);Element rootElement = document.getRootElement();String namespace = rootElement.attributeValue("namespace");List<Element> selectlist = rootElement.selectNodes("//select");Elementforeach(selectlist,namespace);List<Element> updatelist = rootElement.selectNodes("//update");Elementforeach(updatelist,namespace);List<Element> deletelist = rootElement.selectNodes("//delete");Elementforeach(deletelist,namespace);}public void Elementforeach(List<Element> list,String namespace){for (Element element : list) {String id = element.attributeValue("id");String resultType = element.attributeValue("resultType");String paramterType = element.attributeValue("paramterType");String sqlText = element.getTextTrim();MappedStatement mappedStatement = new MappedStatement();mappedStatement.setId(id);mappedStatement.setResultType(resultType);mappedStatement.setParamterType(paramterType);mappedStatement.setSql(sqlText);String key = namespace+"."+id;configuration.getMappedStatementMap().put(key,mappedStatement);}}
}
SqlSession:
//根据条件进行修改
public Integer update(String statementid,Object... params) throws Exception;//根据条件进行删除
public Integer delete(String statementid,Object... params) throws Exception;
DefaultSqlSession:
public class DefaultSqlSession implements SqlSession {private Configuration configuration;public DefaultSqlSession(Configuration configuration) {this.configuration = configuration;}@Overridepublic <E> List<E> selectList(String statementid, Object... params) throws Exception {//将要去完成对simpleExecutor里的query方法的调用simpleExecutor simpleExecutor = new simpleExecutor();MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);return (List<E>) list;}@Overridepublic <T> T selectOne(String statementid, Object... params) throws Exception {List<Object> objects = selectList(statementid, params);if(objects.size()==1){return (T) objects.get(0);}else {throw new RuntimeException("查询结果为空或者返回结果过多");}}@Overridepublic Integer update(String statementid, Object... params) throws Exception {Executor simpleExecutor = new simpleExecutor();MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);Integer i = simpleExecutor.update(configuration, mappedStatement, params);return i;}@Overridepublic Integer delete(String statementid, Object... params) throws Exception {Executor simpleExecutor = new simpleExecutor();MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);Integer i = simpleExecutor.delete(configuration, mappedStatement, params);return i;}@Overridepublic <T> T getMapper(Class<?> mapperClass) {// 使用JDK动态代理来为Dao接口生成代理对象,并返回Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne// 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名// 方法名:findAllString methodName = method.getName();String className = method.getDeclaringClass().getName();String statementId = className+"."+methodName;// 准备参数2:params:args// 获取被调用方法的返回值类型Type genericReturnType = method.getGenericReturnType();// 判断是否进行了 泛型类型参数化if(genericReturnType instanceof ParameterizedType){List<Object> objects = selectList(statementId, args);return objects;}MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);String sql = mappedStatement.getSql();if(sql.startsWith("update")){return update(statementId,args);}if(sql.startsWith("delete")){return delete(statementId,args);}return selectOne(statementId,args);}});return (T) proxyInstance;}
}
SimpleExecutor:
public class simpleExecutor implements Executor {@Override //userpublic <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {// 1. 注册驱动,获取连接Connection connection = configuration.getDataSource().getConnection();// 2. 获取sql语句 : select * from user where id = #{id} and username = #{username}//转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储String sql = mappedStatement.getSql();BoundSql boundSql = getBoundSql(sql);// 3.获取预处理对象:preparedStatementPreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());// 4. 设置参数//获取到了参数的全路径String paramterType = mappedStatement.getParamterType();Class<?> paramtertypeClass = getClassType(paramterType);List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();for (int i = 0; i < parameterMappingList.size(); i++) {ParameterMapping parameterMapping = parameterMappingList.get(i);String content = parameterMapping.getContent();//反射Field declaredField = paramtertypeClass.getDeclaredField(content);//暴力访问declaredField.setAccessible(true);Object o = declaredField.get(params[0]);preparedStatement.setObject(i+1,o);}// 5. 执行sqlResultSet resultSet = preparedStatement.executeQuery();String resultType = mappedStatement.getResultType();Class<?> resultTypeClass = getClassType(resultType);ArrayList<Object> objects = new ArrayList<>();// 6. 封装返回结果集while (resultSet.next()){Object o =resultTypeClass.newInstance();//元数据ResultSetMetaData metaData = resultSet.getMetaData();for (int i = 1; i <= metaData.getColumnCount(); i++) {// 字段名String columnName = metaData.getColumnName(i);// 字段的值Object value = resultSet.getObject(columnName);//使用反射或者内省,根据数据库表和实体的对应关系,完成封装PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);Method writeMethod = propertyDescriptor.getWriteMethod();writeMethod.invoke(o,value);}objects.add(o);}return (List<E>) objects;}@Overridepublic Integer update(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception {// 1. 注册驱动,获取连接Connection connection = configuration.getDataSource().getConnection();// 2. 获取sql语句 : select * from user where id = #{id} and username = #{username}//转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储String sql = mappedStatement.getSql();BoundSql boundSql = getBoundSql(sql);// 3.获取预处理对象:preparedStatementPreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());// 4. 设置参数//获取到了参数的全路径String paramterType = mappedStatement.getParamterType();Class<?> paramtertypeClass = getClassType(paramterType);List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();for (int i = 0; i < parameterMappingList.size(); i++) {ParameterMapping parameterMapping = parameterMappingList.get(i);String content = parameterMapping.getContent();//反射Field declaredField = paramtertypeClass.getDeclaredField(content);//暴力访问declaredField.setAccessible(true);Object o = declaredField.get(params[0]);preparedStatement.setObject(i+1,o);}// 5. 执行sqlint i = preparedStatement.executeUpdate();return i;}@Overridepublic Integer delete(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception {// 1. 注册驱动,获取连接Connection connection = configuration.getDataSource().getConnection();// 2. 获取sql语句 : select * from user where id = #{id} and username = #{username}//转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储String sql = mappedStatement.getSql();BoundSql boundSql = getBoundSql(sql);// 3.获取预处理对象:preparedStatementPreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());// 4. 设置参数//获取到了参数的全路径String paramterType = mappedStatement.getParamterType();Class<?> paramtertypeClass = getClassType(paramterType);if(paramtertypeClass == Integer.class){preparedStatement.setObject(1, params[0]);}else {List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();for (int i = 0; i < parameterMappingList.size(); i++) {ParameterMapping parameterMapping = parameterMappingList.get(i);String content = parameterMapping.getContent();//反射Field declaredField = paramtertypeClass.getDeclaredField(content);//暴力访问declaredField.setAccessible(true);Object o = declaredField.get(params[0]);preparedStatement.setObject(i + 1, o);}}// 5. 执行sqlint i = preparedStatement.executeUpdate();return i;}private Class<?> getClassType(String paramterType) throws ClassNotFoundException {if(paramterType!=null){Class<?> aClass = Class.forName(paramterType);return aClass;}return null;}/*** 完成对#{}的解析工作:1.将#{}使用?进行代替,2.解析出#{}里面的值进行存储* @param sql* @return*/private BoundSql getBoundSql(String sql) {//标记处理类:配置标记解析器来完成对占位符的解析处理工作ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);//解析出来的sqlString parseSql = genericTokenParser.parse(sql);//#{}里面解析出来的参数名称List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();BoundSql boundSql = new BoundSql(parseSql,parameterMappings);return boundSql;}
}
2、Mybatis 相关概念
2.1、对象/关系数据库映射(ORM)
ORM全称Object/Relation Mapping:表示对象-关系映射的缩写。
ORM完成⾯向对象的编程语⾔到关系数据库的映射。当ORM框架完成映射后,程序员既可以利⽤⾯向对象程序设计语⾔的简单易⽤性,⼜可以利⽤关系数据库的技术优势。ORM把关系数据库包装成⾯向对象的模型。ORM框架是⾯向对象设计语⾔与关系数据库发展不同步时的中间解决⽅案。采⽤ORM框架后,应⽤程序不再直接访问底层数据库,⽽是以⾯向对象的⽅式来操作持久化对象,⽽ORM框架则将这些⾯向对象的操作转换成底层SQL操作。ORM框架实现的效果:把对持久化对象的保存、修改、删除等操作,转换为对数据库的操作。
2.2、Mybatis历史与简介
MyBatis是⼀款优秀的基于ORM的半⾃动轻量级持久层框架,它⽀持定制化SQL、存储过程以及⾼级映射。MyBatis避免了⼏乎所有的JDBC代码和⼿动设置参数以及获取结果集。MyBatis可以使⽤简单的XML或注解来配置和映射原⽣类型、接⼝和Java的POJO (Plain Old Java Objects,普通⽼式Java对象)为数据库中的记录。
原是apache的⼀个开源项⽬iBatis, 2010年6⽉这个项⽬由apache software foundation 迁移到了google code,随着开发团队转投Google Code旗下,ibatis3.x正式更名为Mybatis ,代码于2013年11⽉迁移到Github。
iBATIS⼀词来源于“internet”和“abatis”的组合,是⼀个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)
2.3、Mybatis的优势
Mybatis是⼀个半⾃动化的持久层框架,对开发⼈员开说,核⼼sql还是需要⾃⼰进⾏优化,sql和java编码进⾏分离,功能边界清晰,⼀个专注业务,⼀个专注数据。
3、Mybatis基本应用
3.1、快速入门
3.1.1、官网
MyBatis官⽹地址:mybatis – MyBatis 3 | 简介
3.1.2、开发步骤
- ①添加MyBatis的坐标
- ②创建player数据表
- ③编写Player实体类
- ④编写映射⽂件PlayerMapper.xml
- ⑤编写核⼼⽂件SqlMapConfig.xml
- ⑥编写测试类
3.1.3、环境搭建
1、导⼊MyBatis的坐标和其他相关坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.blnp.net</groupId><artifactId>mybatis-demo</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><!--引入依赖--><dependencies><!--mybatis坐标--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.5</version></dependency><!--mysql驱动坐标--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.6</version><scope>runtime</scope></dependency><!--单元测试坐标--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><!--⽇志坐标--><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version></dependency></dependencies>
</project>
2、创建 player 数据表
DROP TABLE IF EXISTS `player`;
CREATE TABLE `player` (`player_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '球员ID',`team_id` int(11) NOT NULL COMMENT '球队ID',`player_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '球员姓名',`height` float(3, 2) NULL DEFAULT NULL COMMENT '球员身高',PRIMARY KEY (`player_id`) USING BTREE,UNIQUE INDEX `player_name`(`player_name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10038 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;-- ----------------------------
-- Records of player
-- ----------------------------
INSERT INTO `player` VALUES (10001, 1001, '韦恩-艾灵顿', 1.93);
INSERT INTO `player` VALUES (10002, 1001, '雷吉-杰克逊', 1.91);
INSERT INTO `player` VALUES (10003, 1001, '安德烈-德拉蒙德', 2.11);
INSERT INTO `player` VALUES (10004, 1001, '索恩-马克', 2.16);
INSERT INTO `player` VALUES (10005, 1001, '布鲁斯-布朗', 1.96);
INSERT INTO `player` VALUES (10006, 1001, '兰斯顿-加洛韦', 1.88);
INSERT INTO `player` VALUES (10007, 1001, '格伦-罗宾逊三世', 1.98);
INSERT INTO `player` VALUES (10008, 1001, '伊斯梅尔-史密斯', 1.83);
INSERT INTO `player` VALUES (10009, 1001, '扎扎-帕楚里亚', 2.11);
INSERT INTO `player` VALUES (10010, 1001, '乔恩-洛伊尔', 2.08);
INSERT INTO `player` VALUES (10011, 1001, '布雷克-格里芬', 2.08);
INSERT INTO `player` VALUES (10012, 1001, '雷吉-巴洛克', 2.01);
INSERT INTO `player` VALUES (10013, 1001, '卢克-肯纳德', 1.96);
INSERT INTO `player` VALUES (10014, 1001, '斯坦利-约翰逊', 2.01);
INSERT INTO `player` VALUES (10015, 1001, '亨利-埃伦森', 2.11);
INSERT INTO `player` VALUES (10016, 1001, '凯里-托马斯', 1.91);
INSERT INTO `player` VALUES (10017, 1001, '何塞-卡尔德隆', 1.91);
INSERT INTO `player` VALUES (10018, 1001, '斯维亚托斯拉夫-米凯卢克', 2.03);
INSERT INTO `player` VALUES (10019, 1001, '扎克-洛夫顿', 1.93);
INSERT INTO `player` VALUES (10020, 1001, '卡林-卢卡斯', 1.85);
INSERT INTO `player` VALUES (10021, 1002, '维克多-奥拉迪波', 1.93);
INSERT INTO `player` VALUES (10022, 1002, '博扬-博格达诺维奇', 2.03);
INSERT INTO `player` VALUES (10023, 1002, '多曼塔斯-萨博尼斯', 2.11);
INSERT INTO `player` VALUES (10024, 1002, '迈尔斯-特纳', 2.11);
INSERT INTO `player` VALUES (10025, 1002, '赛迪斯-杨', 2.03);
INSERT INTO `player` VALUES (10026, 1002, '达伦-科里森', 1.83);
INSERT INTO `player` VALUES (10027, 1002, '韦斯利-马修斯', 1.96);
INSERT INTO `player` VALUES (10028, 1002, '泰瑞克-埃文斯', 1.98);
INSERT INTO `player` VALUES (10029, 1002, '道格-迈克德莫特', 2.03);
INSERT INTO `player` VALUES (10030, 1002, '科里-约瑟夫', 1.91);
INSERT INTO `player` VALUES (10031, 1002, '阿龙-霍勒迪', 1.85);
INSERT INTO `player` VALUES (10032, 1002, 'TJ-利夫', 2.08);
INSERT INTO `player` VALUES (10033, 1002, '凯尔-奥奎因', 2.08);
INSERT INTO `player` VALUES (10034, 1002, '埃德蒙-萨姆纳', 1.96);
INSERT INTO `player` VALUES (10035, 1002, '达文-里德', 1.98);
INSERT INTO `player` VALUES (10036, 1002, '阿利兹-约翰逊', 2.06);
INSERT INTO `player` VALUES (10037, 1002, '伊凯·阿尼博古', 2.08);SET FOREIGN_KEY_CHECKS = 1;
3、编写 PlayEntity 实体
package com.blnp.net.entity;import java.io.Serializable;public class PlayerEntity implements Serializable {/*** 球员ID*/private Integer playerId;/*** 球队ID*/private Integer teamId;/*** 球员姓名*/private String playerName;/*** 球员身高*/private Double height;private static final long serialVersionUID = 1L;public Integer getPlayerId() {return playerId;}public void setPlayerId(Integer playerId) {this.playerId = playerId;}public Integer getTeamId() {return teamId;}public void setTeamId(Integer teamId) {this.teamId = teamId;}public String getPlayerName() {return playerName;}public void setPlayerName(String playerName) {this.playerName = playerName;}public Double getHeight() {return height;}public void setHeight(Double height) {this.height = height;}@Overridepublic String toString() {StringBuilder sb = new StringBuilder();sb.append(getClass().getSimpleName());sb.append(" [");sb.append("Hash = ").append(hashCode());sb.append(", playerId=").append(playerId);sb.append(", teamId=").append(teamId);sb.append(", playerName=").append(playerName);sb.append(", height=").append(height);sb.append("]");return sb.toString();}
}
4、编写 PlayerMapper 映射文件
<?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.blnp.net.dao.PlayerEntityMapper"><!--实体类字段映射关系--><resultMap id="BaseResultMap" type="com.blnp.net.entity.PlayerEntity"><id column="player_id" jdbcType="INTEGER" property="playerId" /><result column="team_id" jdbcType="INTEGER" property="teamId" /><result column="player_name" jdbcType="VARCHAR" property="playerName" /><result column="height" jdbcType="FLOAT" property="height" /></resultMap><!--表通用字段--><sql id="Base_Column_List">player_id, team_id, player_name, height</sql><!--根据主键ID查询--><select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">select<include refid="Base_Column_List" />from playerwhere player_id = #{playerId,jdbcType=INTEGER}</select><!--根据主键ID删除--><delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">delete from playerwhere player_id = #{playerId,jdbcType=INTEGER}</delete><!--单条全字段新增--><insert id="insert" keyColumn="player_id" keyProperty="playerId" parameterType="com.blnp.net.entity.PlayerEntity" useGeneratedKeys="true">insert into player (team_id, player_name, height)values (#{teamId,jdbcType=INTEGER}, #{playerName,jdbcType=VARCHAR}, #{height,jdbcType=FLOAT})</insert><!--单条动态新增--><insert id="insertSelective" keyColumn="player_id" keyProperty="playerId" parameterType="com.blnp.net.entity.PlayerEntity" useGeneratedKeys="true">insert into player<trim prefix="(" suffix=")" suffixOverrides=","><if test="teamId != null">team_id,</if><if test="playerName != null">player_name,</if><if test="height != null">height,</if></trim><trim prefix="values (" suffix=")" suffixOverrides=","><if test="teamId != null">#{teamId,jdbcType=INTEGER},</if><if test="playerName != null">#{playerName,jdbcType=VARCHAR},</if><if test="height != null">#{height,jdbcType=FLOAT},</if></trim></insert><!--根据主键动态更新--><update id="updateByPrimaryKeySelective" parameterType="com.blnp.net.entity.PlayerEntity">update player<set><if test="teamId != null">team_id = #{teamId,jdbcType=INTEGER},</if><if test="playerName != null">player_name = #{playerName,jdbcType=VARCHAR},</if><if test="height != null">height = #{height,jdbcType=FLOAT},</if></set>where player_id = #{playerId,jdbcType=INTEGER}</update><!--根据主键全字段更新--><update id="updateByPrimaryKey" parameterType="com.blnp.net.entity.PlayerEntity">update playerset team_id = #{teamId,jdbcType=INTEGER},player_name = #{playerName,jdbcType=VARCHAR},height = #{height,jdbcType=FLOAT}where player_id = #{playerId,jdbcType=INTEGER}</update>
</mapper>
5、编写MyBatis 核心文件
<?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><!--加载外部的properties文件--><properties resource="jdbc.properties"></properties><!--给实体类的全限定类名给别名--><typeAliases><!--给单独的实体起别名--><!-- <typeAlias type="com.blnp.net.entity.PlayerEntity" alias="playerEntity"></typeAlias>--><!--批量起别名:该包下所有的类的本身的类名:别名还不区分大小写--><package name="com.blnp.net.entity"/></typeAliases><!--environments:运行环境--><environments default="development"><environment id="development"><!--当前事务交由JDBC进行管理--><transactionManager type="JDBC"></transactionManager><!--当前使用mybatis提供的连接池--><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><!--引入映射配置文件--><mappers><mapper resource="mapper/PlayerMapper.xml"></mapper></mappers>
</configuration>
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3307/my_test
jdbc.username=root
jdbc.password=admin@123
6、编写测试代码
package com.blnp.net.demo;import com.blnp.net.dao.PlayerEntityMapper;
import com.blnp.net.entity.PlayerEntity;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;import java.io.IOException;
import java.io.InputStream;
import java.util.List;/*** <p></p>** @author lyb 2045165565@qq.com* @createDate 2024/5/28 17:10*/
public class QueryTest {public static void main(String[] args) throws IOException {//1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");//2.解析了配置文件,并创建了sqlSessionFactory工厂SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);//3.生产sqlSession// 默认开启一个事务,但是该事务不会自动提交SqlSession sqlSession = sqlSessionFactory.openSession();//在进行增删改操作时,要手动提交事务//4.sqlSession调用方法:查询所有selectList 查询单个:selectOne 添加:insert 修改:update 删除:deleteList<PlayerEntity> users = sqlSession.selectList("com.blnp.net.dao.PlayerEntityMapper.selectAll");for (PlayerEntity user : users) {System.out.println(user);}//释放资源sqlSession.close();}@Testpublic void test1() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);//事务自动提交SqlSession sqlSession = sqlSessionFactory.openSession(true);PlayerEntity user = new PlayerEntity();
// user.setPlayerId(10038);user.setTeamId(1003);user.setPlayerName("张飞");user.setHeight(1.89);sqlSession.insert("com.blnp.net.dao.PlayerEntityMapper.insert",user);sqlSession.close();}@Testpublic void test2() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();PlayerEntityMapper mapper = sqlSession.getMapper(PlayerEntityMapper.class);PlayerEntity playerEntity = mapper.selectByPrimaryKey(10038);System.out.println("playerEntity = " + playerEntity.toString());}
}
3.1.4、源码下载
- 快速入门示例工程:mybatis-demo.7z - 蓝奏云
3.2、MyBatis的映射⽂件概述
3.3、核⼼配置⽂件分析
3.3.1、MyBatis核⼼配置⽂件层级关系
3.3.2、MyBatis常⽤配置解析
1、environments标签
其中,事务管理器(transactionManager)类型有两种:
- JDBC:这个配置就是直接使⽤了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作⽤域。
- MANAGED:这个配置⼏乎没做什么。它从来不提交或回滚⼀个连接,⽽是让容器来管理事务的整个⽣命周期(⽐如 JEE 应⽤服务器的上下⽂)。 默认情况下它会关闭连接,然⽽⼀些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻⽌它默认的关闭⾏为。
其中,数据源(dataSource)类型有三种:
- UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
- POOLED:这种数据源的实现利⽤“池”的概念将 JDBC 连接对象组织起来。
- JNDI:这个数据源的实现是为了能在如 EJB 或应⽤服务器这类容器中使⽤,容器可以集中或在外部配置数据源,然后放置⼀个 JNDI 上下⽂的引⽤。
2、mapper标签
该标签的作⽤是加载映射的,加载⽅式有如下⼏种:
<!-- 使⽤相对于类路径的资源引⽤,例如: -->
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/><!-- 使⽤完全限定资源定位符(URL),例如: -->
<mapper url="file:///var/mappers/AuthorMapper.xml"/><!-- 使⽤映射器接⼝实现类的完全限定类名,例如: -->
<mapper class="org.mybatis.builder.AuthorMapper"/><!-- 将包内的映射器接⼝实现全部注册为映射器,例如: -->
<package name="org.mybatis.builder"/>
3、Properties标签
实际开发中,习惯将数据源的配置信息单独抽取成⼀个properties⽂件,该标签可以加载额外配置的properties⽂件。
4、typeAliases标签
类型别名是为Java 类型设置⼀个短的名字。原来的类型名称配置如下:
配置 typeAliases,为 com.blnp.net.domain.User 定义别名为user:
上⾯我们是⾃定义的别名,mybatis框架已经为我们设置好的⼀些常⽤的类型的别名:
5、其它标签
详见官网说明:mybatis – MyBatis 3 | 配置
3.4、Mybatis相应API介绍
3.4.1、SqlSession⼯⼚构建器SqlSessionFactoryBuilder
常⽤API:SqlSessionFactory build(InputStream inputStream),通过加载mybatis的核⼼⽂件的输⼊流的形式构建⼀个SqlSessionFactory对象。
String resource = "org/mybatis/builder/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
其中, Resources ⼯具类,这个类在 org.apache.ibatis.io 包中。Resources 类帮助你从类路径下、⽂件系统或⼀个 web URL 中加载资源⽂件。
3.4.2、SqlSession⼯⼚对象SqlSessionFactory
SqlSessionFactory 有多个个⽅法创建SqlSession 实例。常⽤的有如下两个:
3.4.3、SqlSession会话对象
SqlSession 实例在 MyBatis 中是⾮常强⼤的⼀个类。在这⾥你会看到所有执⾏语句、提交或回滚事务和获取映射器实例的⽅法。执⾏语句的⽅法主要有:
<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)//操作事务的⽅法主要有:
void commit()
void rollback()
3.5、Mybatis的Dao层实现
3.5.1、传统开发⽅式
1、编写UserDao接⼝
public interface UserDao {List<User> findAll() throws IOException;
}
2、编写UserDaoImpl实现
public class UserDaoImpl implements UserDao {public List<User> findAll() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();List<User> userList = sqlSession.selectList("userMapper.findAll");sqlSession.close();return userList;}
}
3、测试传统⽅式
@Test
public void testTraditionDao() throws IOException {UserDao userDao = new UserDaoImpl();List<User> all = userDao.findAll();System.out.println(all);
}
3.5.2、代理开发⽅式
1、代理开发⽅式介绍
采⽤ Mybatis 的代理开发⽅式实现 DAO 层的开发,这种⽅式是我们后⾯的主流。Mapper 接口开发⽅法只需要程序员编写Mapper 接⼝(相当于Dao 接⼝),由Mybatis 框架根据接口定义创建接⼝的动态代理对象,代理对象的⽅法体同上边Dao接⼝实现类⽅法。
Mapper 接⼝开发需要遵循以下规范:
1) Mapper.xml⽂件中的namespace与mapper接⼝的全限定名相同
2) Mapper接⼝⽅法名和Mapper.xml中定义的每个statement的id相同
3) Mapper接⼝⽅法的输⼊参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
4) Mapper接⼝⽅法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
2、编写UserMapper接⼝
3、测试代理⽅式
@Test
public void testProxyDao() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();//获得MyBatis框架⽣成的UserMapper接⼝的实现类UserMapper userMapper = sqlSession.getMapper(UserMapper.class);User user = userMapper.findById(1);System.out.println(user);sqlSession.close();
}