【Java工具】自主实现简易的ORM框架

ORM

  ORM(Object Relational Mapping)对象关系映射,作用是在关系型数据库和对象之间作一个映射,这样,我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道,只要像平时操作Java对象一样操作它就可以了 。也就是将对象与数据库系统中的一条记录对应起来。

我们首先来看一张表。

在这里插入图片描述

在这里插入图片描述
  我们首先看表中表头是每一条记录拥有的东西,那我们是否可以把它变成一个model类,其成员就是表头。这样一条记录就可以变成一个model类的对象了。对象是在内存中,无法长期存储,但访问很快,sql存的表在外存中,可大量存储,但访问很慢。

  对象—>Sql转变:假如做一个学生信息管理系统,我们从view层获得一个学生信息,一个老师早上忙活大半天录了300个学生1,中午吃饭去电脑断电,在内存(RAM)中的数据,断电丢失,全部没有了,白忙活一场。所以我们要把它转到外存去,长期保存,转移到数据库里,看表容易多了(其中有持久化的概念)。

  Sql—>对象的转变:Sql在外存中,访问较慢。我们所有操作是Sql语句,晦涩难懂。而且程序员需要每次将巫师的咒语敲上一遍,太恶心了。所以我们将Sql语句封装起来,给用户外面提供增删改查的方法,参数是修改的数据。

  与人方便:所以我们要完成对象和Sql的双向变化,自动化完成Sql查询。仅仅输入几个简单的命令,能把Sql表中所有数据存在个由对象组成的列表里,也可以根据主键得到想要的记录。其次新new出来的学生信息能快速更新到数据库中,保证数据安全。

任务目标:
1、不需要写SQL,就能执行数据库查询(包括增、删、改);
2、自动执行相关SQL语句;
3、能将查询结果转换成类的对象。

数据库表和类的关系:
表是由记录组成的;记录是由若干字段组成的。
这里,记录是问题的关键。

记录由多个字段和字段的取值(这里可以看成是键值对)组成的;
类的对象是由多个成员和成员的取值(这里可以看成是键值对)组成的。
也就是说,表对应类,记录对应对象,字段对应成员!

任务的核心目标:数据库查询全自动化!
任务的目的:一劳永逸!

任务具体要求:
当把一个表和一个类对应起来后,只要提供表名称和类,就应该能取出这个表中的所有记录;
事实上,在具体操作时,只需要提供类,就可以完成相关操作了!
但,能实现上述要求的数据基础是:
1、知道表和类的对应关系;
2、知道表中的字段名称和类中的成员名称的对应关系。
而上述对应关系完全可以用XML来表达!

MecDatabase database = new MecDatabase();
database.list(StudentInfo.class)	这样调用,就能够得到mec_student_info中的所有记录
database.list(BaseSubjectInfo.class)这样调用,就能够得到mec_base_subject_info中的所有记录

工具编写

首先观察表给出相应的model类

public class StudentInfoModel {

	private String id;
	private String stuId;
	private String name;
	private String nativer;
	private String nation;
	private String schoolDepartmentMajor;
	private boolean status;
	
	public StudentInfoModel() {
	}

	public StudentInfoModel(String id, String stuId, String name, String nativer, String nation,
			String schoolDepartmentMajor, boolean status) {
		this.id = id;
		this.stuId = stuId;
		this.name = name;
		this.nativer = nativer;
		this.nation = nation;
		this.schoolDepartmentMajor = schoolDepartmentMajor;
		this.status = status;
	}

	public String getId() {
		return id;
	}

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

	public String getStuId() {
		return stuId;
	}

	public void setStuId(String stuId) {
		this.stuId = stuId;
	}

	public String getName() {
		return name;
	}

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

	public String getNativer() {
		return nativer;
	}

	public void setNativer(String nativer) {
		this.nativer = nativer;
	}

	public String getNation() {
		return nation;
	}

	public void setNation(String nation) {
		this.nation = nation;
	}

	public String getSchoolDepartmentMajor() {
		return schoolDepartmentMajor;
	}

	public void setSchoolDepartmentMajor(String schoolDepartmentMajor) {
		this.schoolDepartmentMajor = schoolDepartmentMajor;
	}

	public boolean isStatus() {
		return status;
	}

	public void setStatus(boolean status) {
		this.status = status;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((stuId == null) ? 0 : stuId.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		StudentInfoModel other = (StudentInfoModel) obj;
		if (stuId == null) {
			if (other.stuId != null)
				return false;
		} else if (!stuId.equals(other.stuId))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "StudentInfoModel [id=" + id + ", stuId=" + stuId + ", name=" + name + ", nativer=" + nativer
				+ ", nation=" + nation + ", schoolDepartmentMajor=" + schoolDepartmentMajor + ", status=" + status
				+ "]";
	}
	
}

接下来编写相应的XML文件

<mappings>
	<mapping className = "com.mec.orm.model.StudentInfoModel" tableName = "sys_student_info" key = "stuId">
		<column propertyName = "stuId" columnName = "stu_id"></column>
		<column propertyName = "nativer" columnName = "native"></column>
		<column propertyName = "schoolDepartmentMajor" columnName = "s_d_m"></column>
	</mapping>
</mappings>

mapping标签里className表示哪个包下的model类,tableName是表的名称,key代表主键是哪个类的成员(因为命名法不一样,类的成员是驼峰命名法,表的字段是加下划线区分)。

​ column标签中propertyName代表成员的名字,columnName代表表中相对应的名字。

注意:这里的对应映射关系只写了成员名和字段名不相同的部分,因为相同的可以解析出来,就没有必要写了,对于不同的才有必要说明下。

设计类:ClassTableDefination(类表映射)类,仔细观察类和表的相通之处可得出:由类,表名称,主键,成员字段映射组成。其中setKlass(String className)方法,只是暂时认为字段名和成员名相同加到map里。其余所有的getter和setter,根本不是我这层管的,需要解析XML文件,但我现在这只是个类和表映射,不去在乎其他。这就是面向对象思想

ClassTableDefination类(不完整)
    public class ClassTableDefination {

	private Class<?> klass;
	private String tableName;
	private String primaryKey;
	private Map<String, PropertyColumnDefination> propertyColumnMap;
	
	public ClassTableDefination() {
		propertyColumnMap = new HashMap<>();
	}

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

	public void setKlass(String className) {
		try {
			this.klass = Class.forName(className);
			Field[] fields = klass.getDeclaredFields();
			for (Field field : fields) {
				PropertyColumnDefination pcd = new PropertyColumnDefination();
				pcd.setProperty(field);
				pcd.setColumnName(field.getName());
				propertyColumnMap.put(field.getName(), pcd);
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	public String getTableName() {
		return tableName;
	}

	public void setTableName(String tableName) {
		this.tableName = tableName;
	}

	public String getPrimaryKey() {
		return primaryKey;
	}

	public void setPrimaryKey(String primaryKey) {
		this.primaryKey = primaryKey;
	}

	public Map<String, PropertyColumnDefination> getPropertyColumnMap() {
		return propertyColumnMap;
	}

	public void setPropertyColumnMap(Map<String, PropertyColumnDefination> propertyColumnMap) {
		this.propertyColumnMap = propertyColumnMap;
	}
	
	@Override
	public String toString() {
		StringBuffer str = new StringBuffer();
		str.append("类:").append(klass.getName()).append(" <=> ")
			.append("表:").append(tableName);
		for (String key : propertyColumnMap.keySet()) {
			str.append("\n\t").append(propertyColumnMap.get(key));
		}
		str.append("\n").append("主键:").append(primaryKey);
		return str.toString();
	}
}
PropertyColumnDefinition类(不完整)
public class PropertyColumnDefination {

	private Field property;
	private String columnName;
	
	public PropertyColumnDefination() {
	}
	
	public Field getProperty() {
		return property;
	}

	public void setProperty(Field property) {
		this.property = property;
	}

	public String getColumnName() {
		return columnName;
	}

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

设计类:ClassTableFactory(建立类表的工厂),这个类的主要目的就是解析XML文件,完善下层类的信息。并且我们知道一个XML不可能只写一个类和表的对应关系,应该有很多,因此这个类也有存放很多类表映射的容器。

public class ClassTableFactory {

	private static final Map<String, ClassTableDefination> classTablePool;
	
	static {
		classTablePool = new HashMap<>();
	}
	
	public ClassTableFactory() {
	}
	
	public void scanClassTableMappingXMLPath(String path) {
		new XMLParse() {
			
			@Override
			public void dealElement(Element element, int index) {
				ClassTableDefination ctd = new ClassTableDefination();
				String className = element.getAttribute("className");
				ctd.setKlass(className);
				ctd.setPrimaryKey(element.getAttribute("key"));
				ctd.setTableName(element.getAttribute("tableName"));
				
				new XMLParse() {
					
					@Override
					public void dealElement(Element element, int index) {

						String propertyName = element.getAttribute("propertyName");
						String columnName = element.getAttribute("columnName");
						//这里解析到真正的字段名和成员名对应关系,就去下层加方法
						ctd.setRightPropertyColumnMapping(propertyName, columnName);
					}
				}.parseTag(element, "column");
				classTablePool.put(className, ctd);
			}
		}.parseTag(XMLParse.getDocument(path), "mapping");
	}
	
	public ClassTableDefination getClassTableMapping(String className) {
		
		return classTablePool.get(className);
	}
	
	public ClassTableDefination getClassTableMapping(Class<?> klass) {
		
		return getClassTableMapping(klass.getName());
	}
	
}


ClassTableDefination新加方法
public void setRightPropertyColumnMapping(String propertyName, String columnName) {
		PropertyColumnDefination pcd = propertyColumnMap.get(propertyName);
		if (pcd == null) {
			return;
		}
		pcd.setColumnName(columnName);
	}

最终类:MecDataBase这个是最后给用户的类,只需要一句话加参数即可完成用户想要进行的Sql操作。

public class MecDataBase {

	private static MecDataBase me;
	private static Connection connection;
	
	private MecDataBase() {
		PropertiesParse.loadCfgPath("/connection.properties");
		try {
			Class.forName(PropertiesParse.value("driver"));
			connection = DriverManager.getConnection(PropertiesParse.value("url"), 
					PropertiesParse.value("user"), 
					PropertiesParse.value("password"));
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
	public static MecDataBase newInstance() {
		if (me == null) {
			me = new MecDataBase();
		}
		return me;
	}
	
	@SuppressWarnings("unchecked")
	public <T> T getRecordByKey(Class<?> klass, String parimaryKey) {
		try {
			Constructor<?> noneArgConstructor = klass.getConstructor(new Class[] {});
			Object obj = noneArgConstructor.newInstance(new Object[] {});
			//构造SQL语句
			StringBuffer str = new StringBuffer();
			str.append("SELECT ");
			ClassTableFactory ctf = new ClassTableFactory();
			ctf.scanClassTableMappingXMLPath("/ClassTableMapping.xml");
			ClassTableDefination ctd = ctf.getClassTableMapping(klass);
			Map<String, PropertyColumnDefination> pcdMap = ctd.getPropertyColumnMap();
			//需要所有字段名字,去加方法
			str.append(ctd.getAllColumnName()).append(" FROM ").append(ctd.getTableName()).append(" WHERE ").
			append(ctd.getTableName()).append(".").append(pcdMap.get(ctd.getPrimaryKey()).getColumnName())
			.append(" = ").append(parimaryKey);
			//去MySql验证下sql语句是否可以运行
			PreparedStatement state = connection.prepareStatement(str.toString());
			ResultSet rs = state.executeQuery();
			Set<String> keys = pcdMap.keySet();
			while (rs.next()) {
				for (String key : keys) {
					setValue(klass, obj, pcdMap.get(key).getProperty(), rs.getObject(pcdMap.get(key).getColumnName()));
				}
			}
			return (T) obj;
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}

		
		return null;
	}
	
	//完成把一个成员的值赋值给一个对象。要用反射机制,调用setId方法,setxxx方法
	//需要反射机制klass;需要往对象里设置object;给某个成员赋值要知道成员本身,成员的名字;成员赋值成什么值了Value
	/**
	 * 
	 * @param klass	用来进行反射
	 * @param object	是要完成成员赋值的那个对象
	 * @param property	要被赋值的成员
	 * @param value	成员要被赋的值
	 */
	private void setValue(Class<?> klass, Object obj, Field property, Object value) {
		String propertyName = property.getName();
		String methodName = "set" + propertyName.substring(0, 0+1).toUpperCase() + propertyName.substring(1);
		try {
			Method method = klass.getDeclaredMethod(methodName, new Class<?>[] {property.getType()});
			method.invoke(obj, new Object[] {value});
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}
	
	@SuppressWarnings("unchecked")
	public <T> List<T> list(Class<?> klass) {
		
		List<T> result = new ArrayList<>();
		
		ClassTableFactory ctf = new ClassTableFactory();
		ctf.scanClassTableMappingXMLPath("/classTableMapping.xml");
		ClassTableDefination ctd = ctf.getClassTableMapping(klass);
		StringBuffer str = new StringBuffer();
		str.append("SELECT ").append(ctd.getAllColumnName()).append(" FROM ").append(ctd.getTableName());
		Map<String, PropertyColumnDefination> pcdMap = ctd.getPropertyColumnMap();
		Set<String> keys = pcdMap.keySet();
		try {
			PreparedStatement state = connection.prepareStatement(str.toString());
			ResultSet rs = state.executeQuery();
			while (rs.next()) {
				Constructor<?> noneConstructor = klass.getConstructor(new Class<?>[] {});
				Object obj = noneConstructor.newInstance(new Object[] {});
				for (String key : keys) {
					setValue(klass, obj, pcdMap.get(key).getProperty(), rs.getObject(pcdMap.get(key).getColumnName()));
				}
				result.add((T) obj);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		
		return result;
	}
	
	public boolean insertRecord(Object object) {
		boolean ok = false;
		Class<?> klass = object.getClass();	
		ClassTableFactory ctf = new ClassTableFactory();
		ctf.scanClassTableMappingXMLPath("/ClassTableMapping.xml");
		ClassTableDefination ctd = ctf.getClassTableMapping(klass);
		Map<String, PropertyColumnDefination> pcdMap = ctd.getPropertyColumnMap();
		StringBuffer str = new StringBuffer();
		str.append("INSERT INTO ").append(ctd.getTableName());
		Set<String> keys = pcdMap.keySet();
		boolean first = true;
		for (String key : keys) {
			str.append(first ? " (" : ", ").append(ctd.getTableName()).append(".").append(pcdMap.get(key).getColumnName());
			first = false;
		}
		str.append(") VALUES (");
		first = true;
		for (int i = 0; i < pcdMap.size(); i++) {
			str.append(first ? "?" : ", ?");
			first = false;
		}
		str.append(")");
		try {
			PreparedStatement state = connection.prepareStatement(str.toString());
			int i = 1;
			for (String key : keys) {

				String propertyName = pcdMap.get(key).getProperty().getName();
				String methodName = "get" + propertyName.substring(0, 0 + 1).toUpperCase() + propertyName.substring(1);
				Method method = klass.getDeclaredMethod(methodName, new Class<?>[] {});
				Object value = method.invoke(object, new Object[] {});
				state.setObject(i++, value);
			}
			state.executeUpdate();
			ok = true;
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		
		return ok;
	}
	
}


增加方法
    	public String getAllColumnName() {
		StringBuffer str = new StringBuffer();
		
		Set<String> keys = propertyColumnMap.keySet();
		boolean first = true;
		for (String key : keys) {
			str.append(first ? "" : ", ").append(tableName).append(".").append(propertyColumnMap.get(key).getColumnName());
			first = false;
		}
		return str.toString();
	}
测试是否正确
public class Demo {

	public static void main(String[] args) {

//		ClassTableDefination ctd = new ClassTableDefination();
//		ctd.setKlass("com.mec.orm.model.StudentInfoModel");
//		Map<String, PropertyColumnDefination> a = ctd.getPropertyColumnMap();	
//		for (String key: a.keySet()) {
//			System.out.println(key + "   " + a.get(key));
//		}
		
//		ClassTableFactory.scanClassTableMappingXMLPath("/ClassTableMapping.xml");
//		ClassTableDefination ctd = ClassTableFactory.getClassTableMapping(StudentInfoModel.class);
//		System.out.println(ctd);
		
		
		MecDataBase mdb = MecDataBase.newInstance();
//		StudentInfoModel a = mdb.getRecordByKey(StudentInfoModel.class, "02181002");
//		System.out.println(a);
		
//		List<StudentInfoModel> a = mdb.list(StudentInfoModel.class);
//		for (int i = 0; i < a.size(); i++) {
//			System.out.println(a.get(i));
//		}
		
		boolean a = mdb.insertRecord(new StudentInfoModel().setId("xxxxxxxxx").setName("abc")
				.setNation("610").setNativer("01").setSchoolDepartmentMajor("010101").setStuId("xxxxxxxx").setStatus(true));
		System.out.println("最后成功与否" + a);
	}

}

写到现在回头来看,少考虑一个问题。我的数据库连接一直没有关闭,所以代码不完整,需要有关闭数据库连接的方法。将connection、preparement、resultSet提到外面形成成员,然后再写个方法统一关闭即可。连续打开和关闭是十分耗时的,后期学习数据库连接池来处理。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值