前文我们已经实现了一个简单的对象关系库
但是这个库的功能实在是太过于简单了一些,在实际工作环境中我们肯定还需要更多的功能
1. 对象的数据库表名
现在我们一个类可以通过注解@Table来配置表名,这样导致同一类只能映射同一数据库表。
假如我们希望即使是同一个类的实例也能插入到不同的数据库表怎么办?
现在能够把服务后台采集到的数据解析成Data类的对象,但是根据监听的端口不同,即使是相同的Data数据,我也希望存放到不同的数据库表,如端口1采集到的Data数据存到放tb_data_1,端口2采集到的Data数据存放到tb_data_2。
- 我们可以在
Database.insert
方法处额外添加一个参数String tableName
,这样子我们可以在插入的时候直接指定数据库表 - 我们也可以通过继承指定接口来强制每个实例都具有自己的表名
public interface ITable{
String getTableName();
}
public class Data implements ITable{
//各种属性
private String tableName;
@Override
public String getTableName(){
return tableName;
}
//设置数据库表
public void setTableName(String tableName){
this.tableName=tableName;
}
}
在上面代码中我们就可以通过继承ITable接口来配置不同的数据库表了
另外我们还需要对SQLClass进行部分更改
public class SQLClass<T>{
//省略其他属性和函数
public String tableName(T bean){
if(bean intanceof ITable){
return ((ITable)bean).getTableName();
}
return this.tableName;
}
}
2. 对象的非基本类型属性
如果一个对象的属性都是基本类型,那么我们可以直接使用Field.get
和Field.set
来进行配置。
但是如果一个对象的属性是非基本类型,是我们自定义的对象呢?
如果一个对象的属性是非基本类型,我们需要配置Column的时候配置好解析器,能够把属性的值与数据库里的值进行转化
//省略其它非必要信息
public @interface Column{
Class<? extends ISQLFieldParser> parser() default DefaultSQLFieldParser.class
}
public interface ISQLFieldParser<S,T>{//解析器的接口
S parseToSQLValue(T t);
T parseToFieldValue(S s);
}
//默认解析器,即什么也不做,直接进行转换
public class DefaultSQLFieldParser implements ISQLFieldParser {
@Override
public Object parseToSQLValue(Object o) {
return o;
}
@Override
public Object parseToFieldValue(Object value) {
return value;
}
}
因为Java的注解只支持以下类型:
- 原生类型
- String
- Class
- 枚举类型
- 注解类型
- 以上几种类型的数组
所以为了能在注解中配置额外的解析器,所以我们只能选择用Class来配置使用的解析器
public class SQLField{
private IFieldParser parser;
private initParser(){
//这里只是为了简化,实际过程中最好使用getConstructor并且setAccessible(true)来反射获取
ISQLFieldParser parser=Column.parser().newInstance();
}
public Object sqlValue(Object Field){
parser.parseToSQLValue(field);
}
public Object fieldValue(Object sqlValue){
parser.parseToFieldValue(sqlValue);
}
}
接下来我们可以看一下实际过程中如何配置属性解析器
@Table("tb_data_1")
public class Data{
@Column(parser=CommandParser.class,type=INT,length=1)
private Command command;
}
//自定义Command的属性解析器
public class CommandParser implements ISQLFieldParser<Integer,Command>{
public Interger parserToSQLValue(Command command){
return command.getId();
}
public Command parserToFieldValue(Integer integer){
return new Command(integer);
}
}
//自定义Command属性
public class Command{
private id;
public Command(int id){
this.id=id;
}
public int getId(){
return id;
}
}
这样在实际的使用过程中,数据库中的int值会跟Java的Command类自动继续转换
在实际过程中,我们除了在注解
@Column
中进行配置以外,我们还可以用一个Map来存储所有的解析器,每次使用的时候,使用Field.getType()
查找Field对应的Parser,但是这样如果两个Field的类型相同,那么只能使用相同的解析器,无法进行更加细致的个性化配置了。
3. 对象关系库中数据的筛选
在我们操作数据库的时候,可能会使用各种WHERE
关键字来实现筛选,例如查找所有年龄大于10的person数据:SELECT * FROM PERSON WHERE AGE > 10;
。
但是既然存在了对象关系库,我们最好就不要直接编写SQL语句了。那我们怎么实现呢?
1. 数据量总量少的情况,我们可以通过先把所有数据查询出来转化成Java对象,再进行筛选
public class Database{
//查询数据库所有T对象,并将满足predicate条件的对象筛选出来返回其List集合
public <T> List<T> queryList(Class<T> clazz,Predicate<T> predicate)throws SQLException{
List<T> list=new LinkedList<>();
mapBeans(clazz,bean->{
if(predicate(bean)){
list.add(bean);
}
});
return list;
}
}
//查询年龄大于10的Person的List集合
List<Person> list=database.queryList(Person.class,person->person.getAge()>10);
这种方式虽然简单方便直接,但是没有满足条件的数据也需要从数据库中查询出来,并且还要转化成Java对象,效率太低。例如数据库里面有100万个person数据,但是只有10个person的age大于10,难道我们为了要筛选出这个10个数据要把100万个数据都遍历一遍?所以我们需要在SQL查询语句的层面进行一些改善,我们设计出一个Where类作为条件参与对象关系库的数据库操作
2. Where类的设计
public class Database{
//把查询条件作为参数传进去
public <T> int mapBeans(Class<T> clazz,Where where,SQLConsumer<T> consumer)throws SQLException{
SQLClass<T> sqlClass =sqlCache.getSQLClass(clazz);
//查询的SQL语句直接由SQLClass的查询语句与where的SQL语句拼接而成
String sql=sqlClass.selectSQL()+where;
return dbHelper.mapResultSet(sql,
where.getParams(),//where条件中也包含一些参数
resultSet->{
T t=sqlClass.toBean(resultSet);
consumer.accept(t);
});
}
public <T> List<T> queryList(Class<T> clazz,Where where){
List<T> list=new LinkedList<>();
mapBeans(clazz,where,list::add);
return list;
}
}
作为查询的Where类,能够实现大于,小与,等于,在之间,并且两个条件能够互相AND或者OR形成新的条件
public class Where{
private String sql;//用来存储SQL语句
private List<Object> params =new LinkedList();//用来存储条件的值
private Where(String sql,Object ... params){
this.sql=sql;
this.params.add(Arrays.asList(params));
}
//空条件
public static where noWhere(){
return new Where("");
public static Where biggerThan(String field,Object value){
return new Where(field+" > ? ",value)
}
public static Where smallerThan(String field,Object value){
return new Where(field+" < ? ",value)
}
public static Where equal(String field,Object value){
return new Where(field+" = ? ",value)
}
public static Where between(String field,Object start,Object end){
return new Where("( "+field+" between ? and ? )",start,end);
}
//与另外一个条件and
public Where and(Where where){
if (isNoWhere() || where.isNoWhere()) {
sentence += where.sql;
} else {
sentence = " (" + sentence + " and " + where.sentence + ") ";
}
params.addAll(where.params);
return this;
}
//与另外一个条件or
public Where or(Where where){
if (isNoWhere() || where.isNoWhere()) {
sentence += where.sentence;
} else {
sentence = " (" + sentence + " or " + where.sentence + ") ";
}
params.addAll(where.params);
return this;
}
@Override//返回SQL语句
public String toString(){return sql;}
//返回条件值
public Object[] getParams(){return params.toArrary();}
}
接下来我们要进行复杂的条件查询,查询年龄大于10或者身高小于1.70的女性
Where ageWhere=Where.biggerThan("age",10);
Where heigthWhere=Where.smallerThan("height",1.70f);
Where sexWhere=Where.equal("sex",0);//假设0为女性,1为男性
List<Person> list=database.queryList(Person.class,(ageWhere.or(heightWhere)).and(sexWhere));
上面代码我们通过指定Where条件并通过组装形成了更加复杂的Where条件,最后作为参数传入查询中实现了在SQL语句层面的复杂条件查询。
3. 缺点
Where虽然能实现很强大的查询,但是却存在很多缺陷,我们在设置条件的时候还是使用了字符串来配置了field,一旦属性的名称发生重构的时候会发生错误
Where ageWhere=Where.biggerThan("age",10);
实现了对age的筛选,但是如果Person的age的名称发生了改变,那么where条件的参数也得发生改变,这非常不利于重构。
在前面的部分我们讲到了可以通过配置ISQLFieldParser
来实现数据库数据与Java值的解析
public class Person{
@Column(type=INT,parser=SexParser.class)
public Sex sex;
}
public class SexParser implements ISQLFieldParser<Integer,Sex>{
public Integer parseToSQLValue(Sex sex){
return sex.ordinal();
}
public Sex parseToFieldValue(Integer sqlValue){
return Sex.parse(sqlValue);
}
}
public enum Sex{
MALE,FEMALE;
public static parse(int num){
switch(num){
case 0:return MALE;
default:return FEMALE;
}
}
}
上述代码中我们使用枚举类Sex来表示性别,但是数据库中却使用了0和1来存储性别。
我们通过配置解析器能够自动吧数据库里面的0和1转化成Java对象里面的MALE和FEMALE。
但是这种自动转化在设置Where条件的时候失效了
//下面这句Where条件是错误的,因为在前面的对象关系库里面,where的getParams是直接作为参数设置到preparedStatment里面去的。
Where sexWhere=Where.qual("sex",Sex.MALE);
//所以在指定条件的时候只能使用数据库里面对于的0
Where sexWhere=Where.equal("sex",0);
4.结束语
我们设计的简单对象关系库到这里就结束了,因为我是在刚刚转入Java开发的时候开始构思设计这个对象关系库的(我的推动力就是纯粹的懒得把ResultSet一个一个设置到对象里面对应的属性而已)。所以这个对象关系库在设计的时候我没有参考任何现有对象关系库(当时我还不知道有对象关系库这种东西,其实到现在我还是没有正儿八经使用过现有的对象关系库),所以这个库的实现可能跟现在有的对象关系库完全不一样,性能效率结构也大大不如,甚至其中还有好多缺陷没有完善(比如Where条件的缺陷)。
但是作为一个学习的案例,在设计构思这个对象关系库的过程中我也收获了很多东西,包括Java的泛型、设计缓存来提高性能、Template模式、异常的处理与抛出等,希望我的这个库能给大家带来些许的提示。