详解 ORM技术 的基本实现
基本知识点
基本概念:
对象关系映射(Object Relational Mapping)
是通过使用描述对象和数据库之间映射的元数据,
将面向对象语言程序中的对象自动持久化到关系数据库中
本质上就是将数据从一种形式转换到另外一种形式
大致运行机制如下图所示:
优缺点:
- 这也同时暗示着额外的执行开销;
然而,如果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();
}
}
}
那么,本人现在来展示下运行结果:
可以看到:查询操作成功了!