一、在《Mybatis-1-项目环境搭建(Maven)》案例分析
先看一下代码:
// 1.导入mybatis配置文件,抛出异常
// 使用org.apache.ibatis.io.Resources导入mybatis的xml配置文件
InputStream resource = Resources.getResourceAsStream("mybatis.config.xml");
// 2.创建SqlSessionFactory工厂
// 说明:org.apache.ibatis.session.SqlSessionFactory是一个接口
// 使用org.apache.ibatis.session.SqlSessionFactoryBuilder来创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(resource);
// 3.使用工厂生产SqlSession对象
SqlSession sqlSession = factory.openSession();
// 4.通过SqlSesison对象创建mapper接口的代理对象
GuestMapper guestMapper = sqlSession.getMapper(GuestMapper.class);
// 5.通过代理对象来调用selectGuest函数,来访问数据库
List<Guest> guests = guestMapper.selectGuest();
// 6.打印一下结果
for (Guest guest : guests) {
System.out.println(guest);
}
// 7.关闭资源
sqlSession.close();
resource.close();
执行过程:
- 获取mybatis的配置文件
通过类加载器来加载。 - 创建SqlSessionFactory工厂(构建者模式)
Mybatis使用构建者模式,来创建SqlSessionFactory工厂。
构建者模式:可以把创建的细节过程隐藏,我们只需要给构建者(SqlSessionFactoryBuilder)一个需求(resource),就可以得到需要的对象(SqlSessionFactory)。
优势:使用者可以不关注创建的细节过程。 - 通过SqlSessionFactory工厂创建SqlSession对象(工厂模式)
优势:解耦(降低类之间的关系) - 通过SqlSession对象创建mapper接口代理对象(代理模式)
优势:不修改源码的基础上对已有方法进行增强。 - 通过mapper接口代理对象调用函数
- 打印结果
- 关闭资源
二、自定义实现一个Mybatis
让我们自己写一个简单的Mybatis,实现查询。
首先看一下最后的项目结构:
下面就要开始写代码了:
准备:
- mybatis.config.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!-- 配置mybatis --> <configuration> <!-- 指定 MyBatis 数据库配置文件 --> <properties resource="db.properties"/> <!-- 环境配置 --> <!-- 默认使用mysql这个环境 --> <environments default="mysql"> <!-- mysql 数据库环境 --> <environment id="mysql"> <!-- 事务管理:JDBC --> <transactionManager type="JDBC"/> <!-- 数据源配置,POOLED是JDBC连接对象的数据源连接池的实现 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.169.139:3306/seven?characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="tiger"/> </dataSource> </environment> </environments> <!-- mappers告诉了MyBatis去哪里找持久化类的映射文件 注解方式不需要这个 --> <mappers> <!-- 使用xml方式开发,指定持久化类的映射文件 --> <mapper resource="com/seven/mapper/GuestMapper.xml"/> <!-- 使用注解方式开发,指定持久化类接口 --> <!-- <mapper class="com.seven.mapper.GuestMapper"/>--> </mappers> </configuration>
- 实体类 Guest
package com.seven.entity; import java.util.Date; public class Guest { private Integer gid; private String gname; private String gpwd; private Date gbirthday; @Override public String toString() { return "Guest{" + "gid=" + gid + ", gname='" + gname + '\'' + ", gpwd='" + gpwd + '\'' + ", gbirthday=" + gbirthday + '}'; } public Integer getGid() { return gid; } public void setGid(Integer gid) { this.gid = gid; } public String getGname() { return gname; } public void setGname(String gname) { this.gname = gname; } public String getGpwd() { return gpwd; } public void setGpwd(String gpwd) { this.gpwd = gpwd; } public Date getGbirthday() { return gbirthday; } public void setGbirthday(Date gbirthday) { this.gbirthday = gbirthday; } }
- 映射接口 GuestMapper
package com.seven.mapper; import com.seven.entity.Guest; import java.util.List; public interface GuestMapper { List<Guest> selectGuest(); }
- GuestMapper对应的映射文件 GuestMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <mapper namespace="com.seven.mapper.GuestMapper"> <select id="selectGuest" resultType="com.seven.entity.Guest"> select * from t_guest </select> </mapper>
编写mybatis:
先来看一下步骤:
1. 获取mybatis的配置文件
2. 创建SqlSessionFactory工厂(构建者模式)
3. 通过SqlSessionFactory工厂创建SqlSession对象(工厂模式)
4. 通过SqlSession对象创建mapper接口代理对象(代理模式)
5. 通过mapper接口代理对象调用函数
6. 打印结果
7. 关闭资源
-
获取mybatis的配置文件
需要的类:
Resources:获取xml文件的字节对象流package com.seven.mybatis.io; import java.io.InputStream; /** * 获取xml文件的字节对象流 */ public class Resources { /** * 返回文件的字节对象流 * @param filePath * @return */ public static InputStream getResourceAsStream(String filePath) { return Resources.class.getClassLoader().getResourceAsStream(filePath); } }
SqlSessionFactory接口:
MySqlSessionFactory类
XMLConfigParse类:
Configuration类:
Mapper类:
SqlSessionFactoryBuilder类:SqlSessionFactory接口:创建MySqlSession对象。
package com.seven.mybatis.sqsession; public interface SqlSessionFactory { SqlSession openSession(); }
MySqlSessionFactory类(SqlSessionFactory接口实现类):创建MySqlSession对象,并以SqlSession返回。
package com.seven.mybatis.sqsession.impl; import com.seven.mybatis.cfg.Configuration; import com.seven.mybatis.sqsession.SqlSession; import com.seven.mybatis.sqsession.SqlSessionFactory; /** * 创建MySqlSession对象 */ public class MySqlSessionFactory implements SqlSessionFactory { private Configuration cfg; /** * 构造函数 * 输入:配置对象 * @param cfg */ public MySqlSessionFactory(Configuration cfg) { this.cfg = cfg; } /** * 获取SqlSession * @return */ @Override public SqlSession openSession() { return new MySqlSession(cfg); } }
XMLConfigParse类:解析xml。
package com.seven.mybatis.util; import com.seven.mybatis.cfg.Configuration; import com.seven.mybatis.cfg.Mapper; import com.seven.mybatis.io.Resources; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; public class XMLConfigParse { public static Configuration loadConfiguration(InputStream inputStream) { try { //创建Configuration对象 Configuration cfg = new Configuration(); //创建SAXReader对象 SAXReader reader = new SAXReader(); //通过inputStream获取document对象 Document document = reader.read(inputStream); //通过document获取root节点 Element rootElement = document.getRootElement(); //1.解析<property name="" value=""/>节点,获取数据库的四个配置参数 // 1.1获取所有的property节点 List<Element> propertyElements = rootElement.selectNodes("//property"); // 1.2循环遍历property节点 for (Element propertyElement : propertyElements) { //1.2.1获取每个property节点的name属性值和value属性值 String name = propertyElement.attributeValue("name"); String value = propertyElement.attributeValue("value"); //1.2.2对号入座,把数据库配置参数保存到Configuration对象中 if ("driver".equals(name)) { cfg.setDriver(value); } else if ("url".equals(name)) { cfg.setUrl(value); } else if ("username".equals(name)) { cfg.setUsername(value); } else if ("password".equals(name)) { cfg.setPassword(value); } } //2.解析<mapper resource=""/>或<mapper class=''/>节点,获取映射文件的路径或映射接口全名称 // 2.1获取所有的mapper节点 List<Element> mapperElements = rootElement.selectNodes("//mappers/mapper"); // 2.2循环遍历mapper节点 for (Element mapperElement : mapperElements) { //2.2.1获取resource属性 String resource = mapperElement.attributeValue("resource"); //2.2.2判断resource是否存在,存在为xml方式,不存在为注解方式 if (resource != null) { //为xml方式,则要解析xml映射文件 //调用loadMapperConfiguration函数,解析映射文件,返回结果 Map<String, Mapper> mappers = loadMapperConfiguration(resource); //将结果添加进cfg的mappers中 //注意:每一次循环会解析一个配置文件,每次都会返回一个mappers //注意:若在Configuration的setMappers中为this.mappers=mappers //注意:前一个文件的mappers会被替换掉,所以应该使用this.mappers.putAll(mappers) cfg.setMappers(mappers); } } return cfg; } catch (Exception e) { e.printStackTrace(); } return null; } private static Map<String, Mapper> loadMapperConfiguration(String mapperPath) throws Exception { try { //通过mapperPath获取inputStream InputStream inputStream = Resources.getResourceAsStream(mapperPath); //创建Map<String, Mapper>对象 Map<String, Mapper> mappers = new HashMap<>(); //创建SAXReader对象 SAXReader reader = new SAXReader(); //通过inputStream获取document对象 Document document = reader.read(inputStream); //通过document获取root节点 Element rootElement = document.getRootElement(); //1.解析<mapper namespace=""/>节点(根节点),获取namespace属性值 String namespace = rootElement.attributeValue("namespace"); //2.解析<select id="" resultType="">sql语句</select>节点 // 2.1获取所有select节点 List<Element> selectElements = rootElement.selectNodes("//select"); // 2.2循环遍历select节点 for (Element selectElement : selectElements) { //2.2.1获取id属性,即映射类对应的方法名 String id = selectElement.attributeValue("id"); //2.2.2获取resultType属性,即结果集类名全称 String resultType = selectElement.attributeValue("resultType"); //2.2.3获取节点内容,即sql语句 String sqlString = selectElement.getText(); //2.2.4 // 说明:mappers的key为namespace.id,即映射接口的某个方法 // 说明:mappers的value为Mapper类对象,用来存放sql语句和结果集类名全路径 // 构造key String key = namespace + "." + id; // 构造value Mapper mapper = new Mapper(); mapper.setResultType(resultType); mapper.setSqlString(sqlString); // 将结果put到mappers对象中 mappers.put(key, mapper); } //返回结果 return mappers; } catch (Exception e) { throw new Exception("解析映射文件错误"); } } }
Configuration类:xml解析后的配置信息。
package com.seven.mybatis.cfg; import java.util.HashMap; import java.util.Map; public class Configuration { private String driver; private String url; private String username; private String password; private Map<String, Mapper> mappers = new HashMap<>(); public String getDriver() { return driver; } public void setDriver(String driver) { this.driver = driver; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Map<String, Mapper> getMappers() { return mappers; } public void setMappers(Map<String, Mapper> mappers) { this.mappers.putAll(mappers); } }
Mapper类:xml映射文件解析后的配置信息。
package com.seven.mybatis.cfg; public class Mapper { private String resultType; private String sqlString; public String getResultType() { return resultType; } public void setResultType(String resultType) { this.resultType = resultType; } public String getSqlString() { return sqlString; } public void setSqlString(String sqlString) { this.sqlString = sqlString; } }
SqlSessionFactoryBuilder类:解析xml配置文件和映射文件,创建SqlSessionFactory工厂(返回MySqlSessionFactory对象)。
package com.seven.mybatis.sqsession; import com.seven.mybatis.cfg.Configuration; import com.seven.mybatis.sqsession.impl.MySqlSessionFactory; import com.seven.mybatis.util.XMLConfigParse; import java.io.InputStream; public class SqlSessionFactoryBuilder { /** * 解析xml配置文件和映射文件 * 返回:MySqlSessionFactory对象,传入Configuration对象 * @param resource * @return */ public SqlSessionFactory build(InputStream resource) { //解析xml配置文件和映射文件 Configuration cfg = XMLConfigParse.loadConfiguration(resource); //返回MySqlSessionFactory对象,传入Configuration对象 return new MySqlSessionFactory(cfg); } }
SqlSession接口:获取数据库连接对象,返回代理对象,关闭数据库连接。
package com.seven.mybatis.sqsession; import com.seven.mybatis.cfg.Configuration; public interface SqlSession { <T> T getMapper(Class<T> mapperClass); void close(); }
MySqlSession类(SqlSession接口实现类):获取数据库连接对象,返回代理对象,关闭数据库连接。
package com.seven.mybatis.sqsession.impl; import com.seven.mybatis.proxy.MapperProxy; import com.seven.mybatis.cfg.Configuration; import com.seven.mybatis.sqsession.SqlSession; import com.seven.mybatis.util.DataSourceUtil; import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.SQLException; /** * 获取数据库连接对象 * 返回代理对象 * 关闭数据库连接 */ public class MySqlSession implements SqlSession { private Configuration cfg; private Connection conn; /** * 构造函数 * 输入:配置对象 * 获取:数据库连接对象 * @param cfg */ public MySqlSession(Configuration cfg) { this.cfg = cfg; this.conn = DataSourceUtil.getConnection(cfg); } /** * 返回代理对象 * @param mapperClass * @param <T> * @return */ @Override public <T> T getMapper(Class<T> mapperClass) { return (T) Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new MapperProxy(cfg.getMappers(), conn)); } /** * 关闭数据库连接 */ @Override public void close() { if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
DataSourceUtil类:连接数据库。
package com.seven.mybatis.util; import com.seven.mybatis.cfg.Configuration; import java.sql.Connection; import java.sql.DriverManager; public class DataSourceUtil { public static Connection getConnection(Configuration cfg) { try { Class.forName(cfg.getDriver()); return DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword()); } catch (Exception e) { throw new RuntimeException(e); } } }
MapperProxy类:映射类的代理类。
package com.seven.mybatis.proxy; import com.seven.mybatis.cfg.Mapper; import com.seven.mybatis.util.Executor; import com.sun.deploy.net.proxy.ProxyHandler; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.sql.Connection; import java.util.Map; /** * Mapper接口的代理类 */ public class MapperProxy implements InvocationHandler { private Map<String, Mapper> mappers; private Connection conn; /** * 构造函数 * 输入:Mapper对象(结果集类型全限定类名,sql语句) * 输入:数据库连接对象 * @param mappers * @param conn */ public MapperProxy(Map<String, Mapper> mappers, Connection conn) { this.mappers = mappers; this.conn = conn; } /** * 对被代理对象的方法增强 * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //获取声明该方法的类的全限定类名 String declaringClass = method.getDeclaringClass().getName(); //获取该方法名 String methodName = method.getName(); //拼接key(declaringClass.methodName),对应mappers的key String key = declaringClass + "." + methodName; //从mappers中查找key,返回对应的mapper(结果集类型全限定类名,sql语句) Mapper mapper = mappers.get(key); //判断mapper是否存在 // 若存在:调用Executor的selectList(mappers, conn)函数(执行sql语句,将结果集进行封装) // 否则:抛出异常 if (mapper != null) { return new Executor().selectList(mapper, conn); } else { throw new IllegalArgumentException("执行的方法在映射文件中未定义"); } } }
Executor类:JDBC操作。
package com.seven.mybatis.util; import com.seven.mybatis.cfg.Mapper; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.sql.*; import java.util.ArrayList; import java.util.List; import java.util.PropertyPermission; /** * 进行数据库(JDBC)操作 * 执行sql语句 * 对结果集进行封装 */ public class Executor { public <T> List<T> selectList(Mapper mapper, Connection conn) { PreparedStatement pre = null; ResultSet res = null; try { //创建函数返回对象 List<T> entityList = new ArrayList<>(); //取出mapper中的sql数据 String sqlString = mapper.getSqlString(); //取出mapper中的结果集全限定类名,获取该类的class对象,用于后面的反射创建实体类 String resultType = mapper.getResultType(); Class<?> entityClass = Class.forName(resultType); //获取PreparedStatement对象 pre = conn.prepareStatement(mapper.getSqlString()); //执行sql语句,获取结果集 res = pre.executeQuery(); //封装 while (res.next()) { //通过反射实例化实体类对象 T entity = (T) entityClass.newInstance(); //获取结果集的元信息 ResultSetMetaData metaData = res.getMetaData(); //获取元信息中的列数 int columnCount = metaData.getColumnCount(); //循环遍历赋值给实体类对象 //注意:列序号从1开始 for (int i = 1; i <= columnCount; i++) { //在元信息中获取第i列的列名 String columnName = metaData.getColumnName(i); //在结果集中通过列名获取该列的值 Object columnValue = res.getObject(columnName); //给entity赋值,使用java内省机制(借助PropertyDescriptor实现属性的封装) PropertyDescriptor pd = new PropertyDescriptor(columnName, entityClass); //获取pd的写入方法 Method writeMethod = pd.getWriteMethod(); //把获取的列的值给对象赋值 writeMethod.invoke(entity, columnValue); } //把实体类添加进函数返回对象中 entityList.add(entity); } return entityList; } catch (Exception e) { throw new RuntimeException(e); } finally { release(res, pre); } } private void release(ResultSet res, PreparedStatement pre) { if (res != null) { try { res.close(); } catch (SQLException e) { e.printStackTrace(); } } if (pre != null) { try { pre.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
测试类:
import com.seven.entity.Guest; import com.seven.mapper.GuestMapper; import com.seven.mybatis.io.Resources; import com.seven.mybatis.sqsession.SqlSession; import com.seven.mybatis.sqsession.SqlSessionFactory; import com.seven.mybatis.sqsession.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; import java.util.List; public class GuestTest { public static void main(String[] args) throws IOException { //1.获取Mybatis配置文件的inputStream-->resource InputStream resource = Resources.getResourceAsStream("mybatis.config.xml"); //2.通过resource用SqlSessionFactoryBuilder创建SqlSessionFactory //构建者模式 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(resource); //3.通过SqlSessionFactory获取SqlSession对象 SqlSession sqlSession = factory.openSession(); //4.通过sqlSession获取代理对象 GuestMapper mapper = sqlSession.getMapper(GuestMapper.class); //5.调用selectGuest方法 List<Guest> guests = mapper.selectGuest(); //6.遍历打印guests for (Guest guest : guests) { System.out.println(guest); } //7.关闭连接 sqlSession.close(); } }
运行结果:
更新时间:2020-1-13