前情提要
这篇文章主要是解决标题是 MVC模式初体验 properties解析工具 与 Dao层的结合 在最后留的问题,就是怎样去优化下面这部分的代码:
public List<nativeModel> getNativeList() {
sql = "SELECT id,name FROM sys_native_cnst";
List<nativeModel> nativeList = new ArrayList<>();
database db = new database();
ResultSet rs = db.executeQuery(sql);
try {
while(rs.next()) {
String id = rs.getString("id");
String name = rs.getString("name");
nativeList.add(new nativeInfo(id,name));
}
} catch (SQLException e) {
e.printStackTrace();
}
return nativeList;
}
优化结果预期(自我觉得 优化就是做工具,把这一大堆搞成个工具,也适用于其他的model类)
public List<NativeModel> getNatives() {
return list(NativeModel.class);//相当于这个工具直接可以通过类 类型直接返回这个类的对象的List,
}
优化思路
对于上边的代码,我们是采用nativeModel来作为一个例子说明,我们所需要做的优化就是根据需要获得的ModelList的model类,对应到数据库中相应的model类的表,再根据表中拥有的字段来生成sql语句,并且产生一个关于这个类的对象的列表,其重点在于model类和表和其他信息的对应。
细化部分
那其实对于我们要完成的这个工具,在用的时候我们可以直接在这个关于获得各种 getXXXList 的函数中直接调用,而在调用时只需要一个参数就是model类,而我们的这个工具便可以通过这个类而找到对应的表,并且还可以获知表中存在哪些字段,这样我们便可以先产生一个sql语句,并且根据sql语句我们能从表中获取一个ResultSet(结果集),那么这个结果集就是表中每一条记录的集合,那之前说过,其实表中的一条记录对应的是每一个model类的一个对象,此时我们可以根据这个结果集来每一条记录来给model类的对象赋值,便可以产生一个model类对象的集合,则是我们想要的modelList。
【 有了这个工具之后的,那我们就可以肆无忌惮的变换参数,我换成nationModel.class我们便可以得到一系列籍贯,换成majorModel.class就可以得到一系列的专业,然后就是我上篇文章说的就可以采用这些list去初始化你界面的控件部分的内容了,但问题是你想要初始化的控件所需要的内容(内容就是model类的对象的list)所对应的字段在你的表存在。】
听完这个思路你可能觉得简单,事实上其中有很多值得推敲,研究的地方,废话不多说,听我娓娓道来。
firstStep:
(第一步,我们完成Model类与表的对应,还有表中的字段,类中的成员,将表得信息,model类的信息还有表中的字段与model类中的成员对应的信息都保存在classAndTable类中。在后期将通过hashmap将model类和与它相关的classAndTable类的对象作为键值进行存储,以便之后进行访问。
因为我们实例化一个model类的对象的时候,是通过表中的每一条记录来实例化的)
classAndTable类 (随便起的名字,意思是类与表的对应,先确定主要的成员)
public class classAndTable {
private Class<?> klass;
private String table;
private List<PropertyColumn> fieldList;
}
PropertyColumn类(model类中的一个成员对应表中的一个字段)
public class PropertyColumn {
private Field field;
private String column;
}
解释说明:
这个classAndTable是一个可以让model类和表对应起来的一个类 。
- Class<?> klass 表示不同的类;
- String table 类对应数据库中的某一个表
- List < propertyColumn> fieldList 一个model类中有多个成员与表中的多个字段对应形成以一个list,一个字段对应一个成员就形成了一个propertyColumn。
- 这里的PropertyColumn中的对于这个filed的解释为,它是model类的中的一个成员,采用Field来修饰是因为我们并不知道每一个成员的类型,所以我们采用field来定义一是更合理,二是因为我们可以采用反射机制通过成员的名称来初始化PropertyColumn类中filed。
【小结】 上面这两个类的结合将model类和表互相联系的信息已经封装在classAndTable类中,因为界面上有不同的控件,所以也对应的不同的model类,那自然而然也会有不同的表,那我们就得事先通过 配置文件 的方式,产生多个classAndTable类的对象,我们可以对每一个classAndTable类的对象进行初始化,初始化的内容就是model类和它对应的表以及表中的字段和model类中的成员的list列表。初始化完成后就形成一个classAndTable类的对象的list。
然后将个list和对应的model类名称形成键值对存入hashmap中。以后便可以通过model类的名字类在hashmap中找到对应的model类的classAndTable的的信息。
Map<String, ClassTable> classTableMap;//
//string 就是对应的model类的名称
//classAndTable 就是这个model类与它所对应的表的一系列的信息
SecondStep:( 包含xml解析,xml文件)
这一步我们需要实现初始化model类和表的对应的内容)方便以后我们直接可以构造sql语句
xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<mappings>
<mapping class="com.mec.mis.model.NativeModel" table="sys_native_info"
<column property="id" name="id"></column>
<column property="name" name="name"></column>
</mapping>
<mapping class="com.mec.mis.model.NationModel" table="sys_nation_info">
<column property="id" name="id"></column>
<column property="name" name="name"></column>
</mapping>
</mappings>
我们来通过解析这个配置文件中的每一个mapping的方式来将model类和表的对应关系存储起来,(一个mapping事实上就是一个classAndTAble类的内容),为了解释的更清楚,我采用画图的方式来说明存储的方式(以第一个mapping为例子)
xml解析文件(相当于将xml解析后将每一个mapping的内容化作一个classAndTable类的对象进行存储在hashmap中,以后便可以通过hashmap进行键值匹配(根据model类的名称找到对应的classAndTable的对象)
public class ClassTableFactory {
private static final Map<String, ClassTable> classTableMap;
static {
classTableMap = new HashMap<>();
}
public ClassTableFactory() {
}
public void loadMapping(String path) {
try {
new XMLParser() {
@Override
public void dealElement(Element element, int index) {
String className = element.getAttribute("class");
String tableName = element.getAttribute("table");
ClassAndTable ct = new ClassAndTable();
try {
Class<?> klass = Class.forName(className);
ct.setKlass(klass);
ct.setTable(tableName);
new XMLParser() {
@Override
public void dealElement(Element element, int index) {
String id = element.getAttribute("id");
String property = element.getAttribute("property");
String column = element.getAttribute("name");
PropertyColumn pc = new PropertyColumn();
try {
pc.setField(klass.getDeclaredField(property));
pc.setColumn(column);
if (id.length() > 0) {
ct.setId(pc);
}
ct.addProperty(pc);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
}.parse(element, "column");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
classTableMap.put(className, ct);
}
}.parse(XMLParser.loadXml(path), "mapping");
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static ClassAndTable getClassTable(Class<?> klass) {
return classTableMap.get(klass.getName());
}
public static ClassAndTable getClassTable(String className) {
return classTableMap.get(className);
}
xml解析工具
public abstract class XMLParser {
private static DocumentBuilder db;
public XMLParser() {
}
public static Document loadXml(String xmlPath) throws SAXException, IOException {
InputStream is = XMLParser.class.getResourceAsStream(xmlPath);
return loadXml(is);
}
static {
try {
db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
}
public static Document loadXml(InputStream is) throws SAXException, IOException {
return db.parse(is);
}
public abstract void dealElement(Element element, int index);
public void parse(Document document, String tagname) {
NodeList nodeList = document.getElementsByTagName(tagname);
for (int index = 0; index < nodeList.getLength(); index++) {
Element element = (Element) nodeList.item(index);
dealElement(element, index);
}
}
public void parse(Element element, String tagname) {
NodeList nodeList = element.getElementsByTagName(tagname);
for (int index = 0; index < nodeList.getLength(); index++) {
Element ele = (Element) nodeList.item(index);
dealElement(ele, index);
}
}
}
这两步操作已经将完成了model类与对应表信息的存储,存储在hashMap中,相当于map中的键就是某一个model类的名称,值就是与model类对应的表及其字段信息,为之后可以通过classTableFactory类中的方法getClassTable(Class<?> klass),通过将model类作为参数可以获取到对应的键值classAndTable类的一个对象,并且根据这个对象的内容进行构造sql语句,进行从数据库获取数据。
ThirdStep:(反射机制的应用)
classAndTable类方法的完善和propertyColumn类方法的完善。
propertyColumn类
在这里插入代码片
public class PropertyColumn {
private Field field;
private String column;
public PropertyColumn() {
}
public void setProperty(ResultSet resultSet, Object object) {
try {
// 从表的当前一条记录中,获取以column的值为字段名的字段的值
Object value = resultSet.getObject(column);
// 设置field(成员)的可访问性为真,即,private成员都可以被访问
field.setAccessible(true);
// 更改对象object的field成员的值为value
field.set(object, value);
} catch (SQLException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
//后边还有成员的getter和setter的程序就不在此多说了
classAndTable类
public class ClassAndTable {
private Class<?> klass;
private String table;
private List<PropertyColumn> fieldList;
private PropertyColumn id;
public ClassTable() {
fieldList = new ArrayList<>();
}
public void setFieldFromResultSet(ResultSet resultSet, Object object) {
for (PropertyColumn field : fieldList) {
field.setProperty(resultSet, object);
}
}
public void addProperty(PropertyColumn property) {
fieldList.add(property);
}
public String getIdString() {
return table + "." + id.getColumn();
}
public String getColumnString() {
StringBuffer res = new StringBuffer();
boolean first = true;
for (PropertyColumn field : fieldList) {
res.append(first ? "" : ", ")
.append(table).append('.')
.append(field.getColumn());
first = false;
}
return res.toString();
}
//后边的成员getter和setter方法不再说明
}
上述的两段代码,采用了反射机制。
- 因为classAndTable类主要是存储model类所对应的表的数据和字段与model类中的成员的一一对应关系,那自然可以采用这些信息构造出来sql语句主要部分,例如从哪个表获取数据,获取表中哪个字段的数据。
- propertyColumn类中有一个重要的方法就是setProperty(ResultSet resultSet, Object object) ,因为propertyColumn类的对象组成一个list做为classAndtable的一个成员。
我们本身的目的就是根据已知的model类,获知其对应的表,产生一个sql语句从表中获取需要的字段的信息,采用这些信息反过来初始化model类的对象,并形成一个model类对象的list去初始化控件。
所以看下面这段代码,采用 注释 来 解释propertyColumn类中的setProperty( ) 方法和classAndTable类中的setFieldFromResultSet()方法。
database类中的一个方法
public <T> List<T> list(Class<?> klass) {
//事实上这个方法就是我们上篇文章所说的优化部分,把很多相同的代码归纳写为一个工具,只需要给不同的model类就可以。
ClassAndTable ct = ClassTableFactory.getClassTable(klass);
//在进行调用这个方法的时候,只需给出参数model类,这一句代码的意思就是根据这个model类,可以在 ClassTableFactory中的haspMap中根据键值对的得到model类对应的一个classAndTable类的对象,这个对象里边包含的是与这个model有关的表和字段相关信息。
if (ct == null) {
return null;
}
String sql = "SELECT " + ct.getColumnString() + " FROM " + ct.getTable();
//这条语句是产生sql语句。
// ct.getColumnString()是因为ClassAndTable这个类中有这个方法,将classAndtable中的表信息与字段信息组成一个字符串.
// ct.getTable()也是classAndTable类中的一个方法,返回表名称字符串。
// 这条sql语句说明了从哪个表中获取这个表中的哪些字段的信息。
List<T> result = new ArrayList<>();
try {
PreparedStatement state = getConnection().prepareStatement(sql);
ResultSet rs = state.executeQuery();
//rs是一个结果集,就是通过sql语句从数据库获取到的字段信息。相当于多条记录的一个列表。
while(rs.next()) {
Object object = klass.newInstance();
// 产生一个model类的对象.
ct.setFieldFromResultSet(rs, object);
//就是根据这个结果集对产生的每一个model类的对象进行赋值操作,此时下面的赋值操作就是用到了反射机制。
// 之前说过一个表对应一个类,而表中的一条记录对应一个这个类的对象。
// 此时的rs表示类结果集的第一条记录
//ct.setFieldFromResultSet(rs, object)这个方法是classAndFactory中的,因为要通过一条记录对model类的对象进行赋值.
//若一条记录汇总包含两个不同的字段编号和名称,那classAndFactory中有一个List<PropertyColumn> fieldList,那么这个list有两个PropertyColumn元素,一个元素中是编号成员和编号字段对应,另一个元素是名称成员和名称字段对应。
//setFieldFromResultSet(rs, object)方法循环classAndFactory中的List<PropertyColumn> ,
//setProperty(ResultSet resultSet, Object object)方法对利用List<PropertyColumn> 每一个元素中的成员和字段关系进行对model类的一个对象的每个成员进行赋值。
result.add((T) object
}
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
小结:
上边函数中while循环中的三步操作可以根据 classAndFactory类 中的 setFieldFromResultSet() 方法和 propertyColumn类 中的 setProperty( ) 方法进行结合分析。这个函数用来获取对应的modelList,也正是上一篇文章中所要优化部分的代码,在这里只需这一个函数便可以获取不同的modelList,只需在需要产生不同种类的modelList的函数中去调用上边这个函数即可。
那么在这篇文章开头的代码优化结果便是如下了,优化过程虽复杂,但确实做成工具非常的实用。
现在只需要根据传入不同的类就可以获得modelList,再也不需要重复的写类似的代码了。
public List<NativeModel> getNatives() {
return database.list(NativeModel.class);
}
ps: 开开开山怪【原创文章】