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提到外面形成成员,然后再写个方法统一关闭即可。连续打开和关闭是十分耗时的,后期学习数据库连接池来处理。