详解 ORM技术 的基本实现

Youzg LOGO

基本知识点

基本概念:

对象关系映射Object Relational Mapping)
是通过使用描述对象和数据库之间映射元数据
将面向对象语言程序中的对象自动持久化到关系数据库中
本质上就是将数据从一种形式转换到另外一种形式

大致运行机制如下图所示:
ORM 展示


优缺点:

  • 这也同时暗示着额外执行开销
    然而,如果ORM作为一种中间件实现,则会有很多机会做优化
    而这些在手写的持久层不存在
  • 更重要的是用于控制转换的元数据需要提供管理
    但是同样,这些花费要比维护手写方案要少
    而且就算是遵守ODMG规范对象数据库依然需要类级别元数据

在当今最火的两个dao层框架 —— hibernate 与 Mybatis 中,都是应用了此机制
可见此机制的重要性!

那么,接下来,本人就来实现下ORM机制:

基本实现

有关 配置文件:

按照本人上文中所讲,M(即:映射关系)需要存在配置文件
而在我们学了这么久,也能够发现:
如今我们使用的配置文件,大多数都以 XML 和 Properties 的格式存在
但是,这两者也有各自的利弊:

  • xml文件
    配置麻烦,但是当层级关系比较复杂时,xml配置是首选
    因此,大多数的关系配置,都是用的是xml文件进行配置
  • properties文件
    配置简单,但是处理层级关系复杂的信息时,确远不如xml文件配置
    因此,大多数的数值配置,都是用properties文件进行配置

那么,本人就以xml配置文件式,来实现ORM机制
(在下文的代码中,会用到本人往期博文《XML 解析》所讲解的XML文件解析工具,对此有疑问的同学自行复习)


因此,我们的出发点,便是解析这个配置文件

首先,本人来给出一个用于存储 表中字段和类的成员 对应关系的类:

表中字段和类的成员 对应关系 —— Property类:

package edu.youzg.about_orm.core;

import java.lang.reflect.Field;

/**
 * 存储相应 表中字段 和 类的成员 的对应关系 
 */
public class Property {
	private Field property;
	private String column;
	
	public Property() {
	}

	public Field getProperty() {
		return property;
	}
	
	public String getPropertyName() {
		return property.getName();
	}

	public Class<?> getType() {
		return property.getType();
	}
	
	public void setProperty(Field property) {
		this.property = property;
	}

	public String getColumn() {
		return column;
	}

	public void setColumn(String column) {
		this.column = column;
	}
	
	@Override
	public String toString() {
		return "成员:" + property.getName() + " <=> 字段:" + column;
	}
	
}

单张表和类 对应关系 —— TableClassDefinition类:

package edu.youzg.about_orm.core;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 存储 单张表和类 的对应关系
 */

public class TableClassDefinition {
	private Class<?> klass;	//类
	private String table;	//对应的表名
	private Map<String, Property> propertiesMap;	//类中各成员对应表
	
	public TableClassDefinition() {
	}

	/**
	 * 根据 成员对照表 获得 该表中字段和值的对应关系
	 * @return
	 */
	public List<Property> columnNameList() {
		List<Property> columns = new ArrayList<Property>();
		
		for (String key : propertiesMap.keySet()) {
			columns.add(propertiesMap.get(key));
		}
		
		return columns;
	}

	/**
	 * 根据上个方法得到的列表,组建SQL语句的部分
	 * @return
	 */
	public String columnList() {
		StringBuffer str = new StringBuffer();
		
		boolean first = true;
		for (String key : propertiesMap.keySet()) {
			Property property = propertiesMap.get(key);
			str.append(first ? "" : ",");
			str.append(table).append(".").append(property.getColumn());
			first = false;
		}
		
		return str.toString();
	}

	/**
	 * 将名为className类中的 各成员的名称 和 对应关系 存入Map中
	 * @param className
	 */
	public void setKlass(String className) {
		try {
			this.klass = Class.forName(className);
			propertiesMap = new HashMap<String, Property>();
			
			Field[] fields = this.klass.getDeclaredFields();
			for (Field field : fields) {
				String propertyName = field.getName();
				Property property = new Property();
				property.setProperty(field);
				property.setColumn(propertyName);
				
				propertiesMap.put(propertyName, property);
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 设置指定成员的字段信息
	 * @param propertyName
	 * @param column
	 */
	public void setColumn(String propertyName, String column) {
		Property property = propertiesMap.get(propertyName);
		if (property == null) {
			return;
		}
		property.setColumn(column);
	}

	public String getTable() {
		return table;
	}

	public void setTable(String table) {
		this.table = table;
	}

	public Class<?> getKlass() {
		return klass;
	}

	@Override
	public String toString() {
		StringBuffer str = new StringBuffer();
		
		str.append("类:" + klass.getName()).append(" <=> 表:") .append(table);
		for (String key : propertiesMap.keySet()) {
			Property property = propertiesMap.get(key);
			if (property == null) {
				continue;
			}
			str.append("\n\t").append(property);
		}
		
		return str.toString();
	}
	
}

配置文件的解析 —— TableClassFactory类:

package edu.youzg.orm.core;

import java.util.HashMap;
import java.util.Map;

import org.w3c.dom.Element;

import edu.youzg.util.XMLParser;

/**
 * 解析配置文件
 * 将配置文件中 所有 表 与 类 的对应关系 存储在一个map中
 */
public class TableClassFactory {
	private static final Map<String, TableClassDefinition> tableClassPool;
	static {
		tableClassPool = new HashMap<String, TableClassDefinition>();
	}
	
	public TableClassFactory() {
	}

	public static void scanTableClassMapping(String xmlPath) {
		new XMLParser() {
			@Override
			public void dealElement(Element element, int index) {
				String className = element.getAttribute("class");
				String tableName = element.getAttribute("table");
				
				TableClassDefinition tcd = new TableClassDefinition();
				tcd.setKlass(className);
				tcd.setTable(tableName);
				
				new XMLParser() {
					@Override
					public void dealElement(Element element, int index) {
						String propertyName = element.getAttribute("property");
						String columnName = element.getAttribute("name");
						tcd.setColumn(propertyName, columnName);
					}
				}.parseTag(element, "column");
				
				tableClassPool.put(className, tcd);
			}
		}.parseTag(XMLParser.getDocument(xmlPath), "mapping");
	}
	
	public static TableClassDefinition getTableClass(Class<?> klass) {
		return getTableClass(klass.getName());
	}
	
	public static TableClassDefinition getTableClass(String className) {
		return tableClassPool.get(className);
	}
	
}

自动化映射器 —— YouzgOrmMapper:

无论是增、删、改、查,带条件、不带条件
都是对sql语句自动拼串
本人是以带同学们熟悉ORM机制的实现方式目的写本篇博文的,
所以,在此处本人只完成不带条件查询操作:

package edu.youzg.about_orm.core;

import edu.youzg.util.PropertiesParser;

import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class YouzgOrmMapper {
	private static Connection connection;
	
	public YouzgOrmMapper() {
	}
	
	public static void loadDatabaseConfig(String configFile)
			throws ClassNotFoundException, SQLException {
		PropertiesParser.loadProperties(configFile);

		Class.forName(PropertiesParser.value("driver"));
		connection = DriverManager.getConnection(PropertiesParser.value("url"),
				PropertiesParser.value("user"),
				PropertiesParser.value("password"));
	}
	
	public static void loadOrmMapping(String mappingPath) {
		TableClassFactory.scanTableClassMapping(mappingPath);
	}

	/**
	 * 将指定类 的 对象 的信息,从数据库中提取出来,
	 * 并存入一个List中,作为返回值返回
	 * @param klass 要读取的类的Class对象
	 * @param <T> 目标类的类型
	 * @return 数据库中该类的所有数据的List
	 */
	public static <T> List<T> list(Class<?> klass) {
		List<T> result = new ArrayList<T>();
		StringBuffer str = new StringBuffer("SELECT ");
		
		TableClassDefinition tcd = TableClassFactory.getTableClass(klass);
		str.append(tcd.columnList()).append(" ")
			.append("FROM ").append(tcd.getTable());
		List<Property> propertyList = tcd.columnNameList();
		
		try {
			PreparedStatement state = connection.prepareStatement(str.toString());
			ResultSet rs = state.executeQuery();
			while (rs.next()) {
				try {
					@SuppressWarnings("unchecked")
					T value = (T) klass.newInstance();
					for (Property property : propertyList) {
						Object obj = rs.getObject(property.getColumn());
						setValue(klass, value, property, obj);
					}
					result.add(value);
				} catch (InstantiationException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
		return result;
	}
	
	/**
	 * 为 指定对象 的 指定成员 设置指定的值
	 * @param klass	用来进行反射
	 * @param object 是要完成成员赋值的那个对象
	 * @param property 反射时所要用到的所有方法的参数的类型集合
	 * @param value 成员的值
	 */
	private static void setValue(Class<?> klass, Object object, Property property, Object value) {
		String propertyName = property.getPropertyName();
		String methodName = "set" + propertyName.substring(0, 1).toUpperCase()
				+ propertyName.substring(1);
		try {
			Method method = klass.getDeclaredMethod(methodName, new Class<?>[] {property.getType()});
			method.invoke(object, new Object[] {value});
		} catch (Exception e) {
		}
	}

}

测试:

首先,本人来构造一张表:

sql脚本:

create table fan_info
(
	id int auto_increment,
	fan_name varchar(15) not null,
	age int not null,
	address varchar(80) null,
	constraint fan_info_pk
		primary key (id)
);

现在,本人来向表中填入如下数据:
表中数据 展示


实体类:

现在,本人再来提供O(即:存储数据的对象)的代码:

package edu.youzg.about_orm.entity;

/**
 * @Author: Youzg
 * @CreateTime: 2020-05-19 16:52
 * @Description: 带你深究Java的本质!
 */
public class Fan {
    private int id;
    private String name;
    private int age;
    private String address;

    public Fan() {
    }

    public Fan(int id, String name, int age, String address) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "编号为:[" + this.id + "]粉丝:["
                + this.name + "]今年["
                + this.age + "]岁了,出生地为:["
                + this.address + "]";
    }

}

接下来,本人来展示下 测试用的配置文件

配置文件:

映射文件 —— morm.tcm.xml:

<?xml version="1.0" encoding="UTF-8"?>
<mappings>
	<mapping class="edu.youzg.about_orm.entity" table="fan_info">
		<column name="fan_name" property="name"></column>
	</mapping>
</mappings>

数据库连接参数 配置文件 —— youzgOrm.properties:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/dbstudy
user=root
password=123456

那么,现在本人来使用下上文所做的小工具:

测试类:

package edu.youzg.about_orm.test;

import edu.youzg.about_orm.core.YouzgOrmMapper;
import edu.youzg.about_orm.entity.Fan;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.sql.SQLException;
import java.util.List;

/**
 * @Author: Youzg
 * @CreateTime: 2020-05-19 16:47
 * @Description: 带你深究Java的本质!
 */
public class YouzgTest {

    public static void main(String[] args) {
        try {
            YouzgOrmMapper.loadDatabaseConfig("src/youzgOrm.properties");
            YouzgOrmMapper.loadOrmMapping("src/morm.tcm.xml");

            List<Fan> fanList = YouzgOrmMapper.list(Fan.class);
            for (Fan aFan : fanList) {
                System.out.println(aFan);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}

那么,本人现在来展示下运行结果
运行结果 展示
可以看到:查询操作成功了!


posted @ 2020-05-19 19:12  在下右转,有何贵干  阅读(240)  评论(0编辑  收藏  举报