完成手写MyMybatis自定义框架。重新温习了以下技术,加深自己对Mybatis框架的深层次理解。
使用到的技术有
整个自定义框架分成三部分功能
-
解析XML文件封装成实体类对象
-
Mapper用于封装UserMapper.xml中元素
-
Configuration用于解析并封装mybatis-config.xml中元素
-
-
通过JDK动态代理,生成UserMapper接口的代理对象
-
SqlSession:运行XML中的SQL语句,JDBC访问数据库,通过反射封装查询的结果集
项目结构如下:
sql准备
CREATE TABLE USER (
id INT(11) NOT NULL AUTO_INCREMENT,
username VARCHAR(20) NOT NULL,
birthday DATE DEFAULT NULL,
sex CHAR(1) DEFAULT '男',
address VARCHAR(50) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8
mybatis-config.xml代码
<?xml version="1.0" encoding="UTF-8" ?> <configuration> <!--mybatis环境的配置--> <environments default="development"> <!--通常我们只需要配置一个就可以了, id是环境的名字 --> <environment id="development"> <!--事务管理器:由JDBC来管理--> <transactionManager type="JDBC"/> <!--数据源的配置:mybatis自带的连接池--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/day06"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <!--加载映射文件,放到src下即可--> <mapper resource="userMapper.xml"/> </mappers> </configuration>
userMapper.xml代码实现
<?xml version="1.0" encoding="UTF-8" ?> <!--<mapper namespace="org.mybatis.example.BlogMapper">--> <mapper namespace="dao.UserMapper"> <!-- 查询语句 resultType:返回的实体类的类型,类全名 --> <select id="findAllUsers" resultType="pojo.User"> select * from user </select> </mapper>
UserMapper.java代码实现:
package dao; import pojo.User; import java.util.List; public interface UserMapper { /* 查询所有用户 */ List<User> findAllUsers(); }
User实体类代码实现:
package pojo; import java.util.Date; public class User { private Integer id; private String username; private Date birthday; private String sex; private String address; public User(){ } public User(Integer id, String username, Date birthday, String sex, String address) { this.id = id; this.username = username; this.birthday = birthday; this.sex = sex; this.address = address; } //get、set、toString方法 }
Configuration.java代码实现
package framework; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.Node; import org.dom4j.io.SAXReader; import java.beans.PropertyVetoException; import java.util.HashMap; import java.util.List; import java.util.Map; public class Configuration { //1.连接数据库的四大参数 private String driver; private String url; private String username; private String password; //2.Map集合封装多个映射文件,key是String类型,value是Mapper private HashMap<String,Mapper> mapper = new HashMap<String,Mapper>(); //3.提供数据源,即数据库连接池引用private DataSource dataSource; private ComboPooledDataSource dataSource; public Configuration(){ try { //1. 从src路径加载mybatis-config.xml配置文件,创建输入流 //2. 使用dom4j得到文档对象 Document document = new SAXReader().read(Configuration.class.getClassLoader() .getResourceAsStream("mybatis-config.xml")); //解析核心xml配置文件 loadMybatisConfig(document); //2.创建数据源 createDataSource(); //3.加载UserMapper.xml loadMapper(document); } catch (Exception e) { e.printStackTrace(); } } private void loadMapper(Document document) throws DocumentException { //1. 读取mapper中的resource属性值 List<Node> nodes = document.selectNodes("//mapper"); //遍历 for (Node node : nodes) { // 1. 使用XPath读取所有mapper元素 // 2. 遍历每个mapper元素 // 3. 读取mapper的resource属性值 Element element = (Element) node; String mapperResource = element.attributeValue("resource"); //2. 通过resource读取它对应的XML文件,得到namespace,id,resultType,sql的值 // 1. 使用类对象,读取输入流下面resource // 2. 创建文档对象 // 3. 读取根元素mapper // 4. 读取namespace属性 // 5. 读取根元素下的一个select标签 // 6. 得到id,resultType,sql内容 Document mapperDom = new SAXReader().read(Configuration.class .getClassLoader().getResourceAsStream(mapperResource)); Element rootElement = mapperDom.getRootElement(); String namespace = rootElement.attributeValue("namespace"); List<Element> selectList = rootElement.elements("select"); for (Element selectElement : selectList) { String id = selectElement.attributeValue("id"); String resultType = selectElement.attributeValue("resultType"); String sql = selectElement.getTextTrim(); //3. 封装成Mapper对象 // 1. 创建一个自定义的Mapper对象,封装上面三个属性 // 2. 再封装namespace属性 // 3. 将封装好的mapper对象添加到this的mappers属性中,其中键是namespace+"."+id,值是自定义的mapper对象。 Mapper mapper = new Mapper(); mapper.setNamespace(namespace); mapper.setId(id); mapper.setResultType(resultType); mapper.setSql(sql); this.mapper.put(namespace+","+id,mapper); } } } private void createDataSource() throws PropertyVetoException { //使用c3p0连接池 ComboPooledDataSource ds = new ComboPooledDataSource(); //在代码中设置连接池的属性 ds.setDriverClass(this.driver); ds.setJdbcUrl(this.url); ds.setPassword(this.password); ds.setUser(this.username); //创建好的数据源赋值给成员变量 this.dataSource = ds; } /**解析xml文件*/ private void loadMybatisConfig(Document document) { //得到properties元素 List<Node> nodes = document.selectNodes("//property"); //遍历 for (Node node : nodes) { //强转 Element element = (Element) node; //获取属性值对应的value String name = element.attributeValue("name"); String value = element.attributeValue("value"); //判断 将那个属性封装给那个对象中属性 switch (name){ case "driver": this.driver = value; break; case "url": this.url = value; break; case "username": this.username = value; break; case "password": this.password = value; break; } } } //get、set、toString方法省略 }
Mapper.java代码实现:
package framework; /** 封装UserMapper.xml属性 */ public class Mapper { //1. 创建包 //2. 创建实体类:Mapper包含4个属性:namespace,id,resultType,sql //3. 重写toString()方法,方便后期测试看到封装的结果 //4. 生成get和set方法 //5. 一个Mapper对象代表一条要操作的查询语句对象 private String namespace; private String id; private String resultType; private String sql; //get、set、toString方法省略 }
SqlSession.java代码实现
package framework; import com.mchange.v2.c3p0.ComboPooledDataSource; import java.lang.reflect.*; import java.sql.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /* sql会话类 */ @SuppressWarnings("all") public class SqlSession { //定义方法获取mapper /** 创建UserMapper接口的代理对象 @param clazz 接口类对象 @return */ public <T> T getMapper(Class<T> clazz) {//Class<T> clazz = UserMapper.class /* 返回的是接口的代理对象 参数1:类加载器 参数2:所有实现的接口 参数3:回调函数,用来处理每个方法 */ // 参数1:类加载器 ClassLoader classLoader = SqlSession.class.getClassLoader(); // 参数2:所有实现的接口 Class[] interfaces = {clazz}; //proxyMapper表示传递的接口的代理类对象 T proxyMapper= (T) Proxy.newProxyInstance(classLoader,interfaces , new InvocationHandler() { //参数1:生成的代理对象 //参数2:要调用的方法 例如UserMapper接口中的方法:findAllUsers //参数3:方法的参数 //返回值:方法的返回值 List<User> findAllUsers(); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //创建核心配置类对象 Configuration config = new Configuration(); //使用对象获取数据源 ComboPooledDataSource ds = config.getDataSource(); //从数据库连接池获取连接 Connection conn = ds.getConnection(); //获取mapper集合对象 HashMap<String, Mapper> map = config.getMapper(); //获取map集合的key //获取namespace即全类名 String namespace = clazz.getName(); // System.out.println("namespace = " + namespace); //获取id即接口的方法名 String id = method.getName(); // System.out.println("id = " + id); //根据key获取map集合中的value String key = namespace + "," + id; Mapper mapper = map.get(key); //获取mapper类的属性sql String sql = mapper.getSql(); //获取结果集resultType String resultType = mapper.getResultType(); //得到pojo类的对象 即User对象 Class objClass = Class.forName(resultType); List list = queryForList(conn,sql,objClass); return list; } }); return proxyMapper; } private List queryForList(Connection conn, String sql, Class clazz) throws Exception { List users = new ArrayList(); //1.通过连接对象得到预编译的语句对象 PreparedStatement ps = conn.prepareStatement(sql); //2.执行SQL语句,得到结果集 ResultSet rs = ps.executeQuery(); //3.遍历结果集,将每一行记录封装成一个User对象 while (rs.next()) { Object user = clazz.getConstructor().newInstance(); //得到User类中所有的成员变量 /* private Integer id; private String username; private Date birthday; private String sex; private String address; */ Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) {//field 可以是:id username birthday sex address //得到成员变量的名字 String name = field.getName(); //暴力反射 field.setAccessible(true); //遍历成员变量给每个成员变量赋值 //从结果集中取出所有的数据 //rs.getObject(name) 表示根据数据表的列名取出数据表中的列值 因为User类中的成员变量名必须和数据表列名一致 //例如: name 的值是birthday 那么这里 rs.getObject(name)---》rs.getObject("birthday")获取数据表的生日1980-10-24 Object table_value = rs.getObject(name); //void set(Object obj, Object value)给成员变量赋值,参数1:对象名 参数2:要赋的值 field.set(user, table_value); } //4.将user对象添加到集合中 users.add(user); } rs.close(); ps.close(); conn.close(); return users; } }
Test.java测试类的代码实现
package test; import dao.UserMapper; import framework.Configuration; import framework.SqlSession; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.Node; import org.dom4j.io.SAXReader; import org.junit.Test; import pojo.User; import java.io.InputStream; import java.util.List; public class test { @Test public void findAllUsers() { //创建会话对象 SqlSession session = new SqlSession(); //得到DAO接口对象:参数是接口,返回的是实现类。得到是它的代理对象 UserMapper mapper = session.getMapper(UserMapper.class); //调用接口中的方法 List<User> userList = mapper.findAllUsers(); //遍历集合 for (User user : userList) { System.out.println(user); } } }