# 数据实体
EOS提供了数据实体(持久化实体、查询实体)和命名SQL两种类型的数据服务来实现对数据库的访问,对关系型数据库的访问做了封装,改变以往通过底层JDBC的编程,直接运行sql语句进行数据库查询和更新的做法,从而屏蔽技术细节,方便用户开发,并提供更好的跨数据库的移植性。
接下来介绍数据实体相关概念和功能,命名SQL的介绍参见命名SQL章节相关说明。
# 数据集
数据集是多个数据实体的一个集合,具体的表现形式就是多个实体类型定义在一个.xsd文件中。这个.xsd文件就被称作数据集。
newdataset就是一个数据集,对应的文件就是newdataset.xsd。 该数据集里定义了3个持久化实体:Product、ProductService、Service。 这3个实体的类型全名分别是:
- com.primeton.eos.newdataset.Product
- com.primeton.eos.newdataset.ProductService
- com.primeton.eos.newdataset.Service
# 实体
# 基本概念
EOS中,实体就是指通过XSD来定义的SDO的数据对象(commonj.sdo.DataObject)。
一个实体会有类型,类型会包含一些属性,每个属性又都有自己的类型。
这就类似于一个Java Class的定义,每个Class都有自己的属性,每个属性也会有相应的Class。
可以把DataObject和JavaBean进行类比,2种对象都是用于存放数据的。
在Stuido中可以创建3种不同类型的实体,分别是实体、持久化实体、查询实体,以及4种关联关系。如下图:
这些概念会在后面逐一介绍。
# 示例
下面是一个实体的例子:
在数据集中创建一个User实体,可以通过在选用板中选中实体拖动到数据集视图中,并设置名称和属性。
在数据集中如下图显示:
User实体包括3个属性:userName(String)、age(Int)、gender(String),其中gender的默认值是male。
说明
对于设置了默认值的属性,创建对象以后,如果没有操作过该属性,则该属性的值就是默认值。
在数据集中双击User实体,出现属性设置框,如下图:
设置实例类属性,这个属性用来说明该实体所对应的静态SDO类型,这里没有设置,说明没有静态的SDO类型与该实体对应。
说明
静态的SDO类型实际上是实现了commonj.sdo.DataObject接口,并且对于该实体的属性提供了setter/getter的Java Class。
# 持久化实体
持久化实体是和数据库表有映射关系的实体。每个持久化实体必须对应一个数据库表。
持久化实体里的简单类型的属性可以直接和列做映射。 如果是个复杂类型的属性则需要通过关联关系来设置映射。
通过操作持久化实体可以对数据库表的记录进行增删改查操作。
下表中,显示了持久化实体属性的数据类型和数据库表的字段类型的映射关系。
显示类型(显示在Studio上供用户选择的类型) | SDO类型 | DAS类型 | Jdbc类型 |
---|---|---|---|
Byte | Byte | byte | TINYINT |
Short | Short | short | SMALLINT |
Float | Float | float | FLOAT |
Int | Int | int | INTEGER |
Double | Double | double | DOUBLE |
Long | Long | long | BIGINT |
Decimal | Decimal | big_decimal | NUMERIC |
Boolean | Boolean | boolean | BIT |
String | String | string | VARCHAR |
Date | Date | date | DATE |
Time | Time | timeString | TIME |
TimeStamp | Date | timestamp | TIMESTAMP |
ClobString | String | com.primeton.das.entity.impl.lob.type.ClobStringType | CLOB |
BlobByteArray | Bytes | com.primeton.das.entity.impl.lob.type.BlobByteArrayType | BLOB |
ClobFile | String | com.primeton.das.entity.impl.lob.type.ClobFileType | CLOB |
BlobFile | String | com.primeton.das.entity.impl.lob.type.BlobFileType | BLOB |
说明
表中第一列显示类型就是持久化实体属性的数据类型。 用户在Studio创建持久化实体时可以选择属性的类型,后面3列的类型对用户是不可见的,会根据选择的显示类型自动生成。
持久化实体属性设置如下图:
持久化实体的其它设置项说明如下:
持久化实体的设置项 | 说明 |
---|---|
表 | 说明持久化实体要和哪张表做映射。 |
Schema | 说明映射的表是在哪个Schema中,不写就认为连接数据库时用户对应的Schema。 |
主键生成策略 | native:利用数据库本身提供的主键自增的功能自动生成主键。 如Oracle就要使用sequence来实现自动生成主键,sequence的名字是"eos_sequence",而DB2、MySql、SQLSERVER、SYBASE、INFORMIX则可以在建表时指定主键列是自增长的。assigned:由用户自己分配一个主键。uuid:由程序自动生成一个32位的无连续性的字符串作为主键。注意uuid的主键生成策略不支持在一台机器上搭建的集群环境。 |
# 相关约束
- 持久化实体里不能定义属性名为id的属性(主键对应的属性可以使用id这个名称),id是保留关键字;
- 同一数据集中持久化实体名称具有唯一性,大小写敏感;
- 允许不同的持久化实体对应同一张数据库表;
- 不支持继承关系的映射。
# 简单示例
com.eos.das.entity.IDASSession是对java.sql.Connection的封装,可以使用该接口对持久化实体进行增删改查的操作。
持久化实体com.eos.User定义如下,该实体对应的数据库表为EOS_USER,下面的代码的作用是对USER表新增一条记录。
//TODO:beginTransaction();
//TODO:getConnection();
//创建IDASSession,用于操作实体
IDASSession session = DASManager.createDasSession(connection);
//创建一个User.
DataObject dataObject = DataFactory.INSTANCE.create("com.eos","User");
dataObject.setString("userId","0001");
dataObject.setString("userName","xiaohu");
//保存User.
session.insertEntity(dataObject);
session.close();
//TODO:closeConnection();
//TODO:commitTransaction();
# 查询实体
通过定义一句查询的sql语句或一个数据库的视图来指明数据是如何获取的,并且设置获取后的数据如何和一个实体做映射,则这个实体就被称作查询实体。 查询实体用于映射的属性都是简单属性。
可以对不同类型的数据库指定查询的sql语句或视图名称,因为每种数据库的sql语法有差异,并且也不是每种数据库都支持视图;但是必须指定一个default类型的配置,因为EOS会在程序运行时根据数据库连接自动判断数据库类型,然后根据对该种类型的数据库配置来获取数据,如果没有找到对该种数据库的配置,则会使用default类型的配置。
查询实体的定义界面如下:
可以对不同类型的数据库设置查询方式(sql或view),写上查询语句、实体名称,选好数据源,然后点击映射属性,就会在下面的反射属性里生成属性和列的映射。以后使用该查询实体查询时,数据库记录就会按照设置的映射关系,把列的值放入到对应的属性里。
# 相关约束
- 查询实体里不能定义属性名叫id的属性(唯一标识对应的属性可以使用id这个名称),id是保留关键字;
- 同一数据集中查询实体名称具有唯一性;
- 查询实体只能用于查询,不可以对其执行增删改的操作;
- 如果指定的查询sql语句或视图中有2个相同的列名,需要起不同的别名来区分。
注意
查询实体允许用户通过指定某一列或几列的值来做为查询实体的唯一标识。 如果用户没有指定,EOS会自动使用一个常量来作为该查询实体唯一标识。 但是如果用默认的唯一标识,就不能使用持久化实体和该查询实体做单向1:1关联和单向N:1关联,否则对持久化实体做操作会抛异常。
# 简单示例
可以使用com.eos.das.entity.IDASSession的接口对查询实体进行查询操作。
查询实体的定义如下图所示,该实体的全名是com.eos.UserQueryEnity。
//TODO:getConnection();
//创建IDASSession,用于操作实体
IDASSession session = DASManager.createDasSession(connection);
//创建一个IDASCriteria,指明对哪个实体操作,用于查询
IDASCriteria criteria =
DASManager.createCriteria("com.eos.UserQueryEnity");
//增加一个查询条件 User.userId = '0001'
criteria.add(ExpressionHelper.eq("userId","0001"));
//查询出一个UserQueryEntity对象,
DataObject userQueryEntity = session.queryEntity(criteria);
session.close();
//TODO:closeConnection();
com.eos.UserQueryEntity是通过"select * from EOS_USER"这句sql定义的。 上面代码实际上等于执行了这样的查询"select * from (select * from EOS_USER) t where t.USERID='0001'"。
注意
由于Informix数据库本身不支持SQL方式,所以在使用Informix时需要将查询方式设置为View。如下图所示:
# 关联
这里介绍的关联只适用于持久化实体和查询实体,不包括一般实体。因此下面的介绍中会用"实体"来代替"持久化实体"和"查询实体"。
关联就是指在一个实体对象中有另一个实体对象的引用。 EOS支持4种关联关系:单向1:1关联、单向1:N关联、单向N:1关联、双向1:N关联。
# 基本规则
有持久化实体PersistentEntityA、PersistentEntityB、查询实体QueryEntityC。
- 2个持久化实体之间只能建立一个关联关系,但不限定是由哪个实体发起。 就是说既可以是从PersistentEntityA关联到PersistentEntityB,也可以是从PersistentEntityB关联到PersistentEntityA。(双向1:N被认为是一个关联关系)
- 1个持久化实体和1个查询实体之间只能建立一个关联关系,而且必须是从持久化实体关联到查询实体。
EOS支持的4种关联关系,如无特别说明,则只有上面2条规则。
# 延迟加载
延迟加载就是指当查询出一个主端的对象时,其关联的对象并不会立即被连带的查出。 这在1:N关联时对于性能有影响,因为1端对象关联的N端对象可能非常多。每次查询1端实体就把关联的N端实体查询出来,是非常慢的。
说明
单向1:1关联没有延迟加载。
双向1:N关联就是单向1:N关联和单向N:1关联的组合。所有可用的功能和约束也是单向1:N关联和单向N:1关联的并集。
# 单向1:1关联
# 简介
单向1:1关联不需要设置关联属性,直接通过2个实体的主键关联。
如com.eos.User和com.eos.UserInfo两个实体是单向1:1关联。那么在实体上表现出来就是User里有一个UserInfo类型的属性。通过User就可以访问到UserInfo的属性。
user.getString("userInfo/phone");
EOS限制如果2个实体要建立单向1:1关联,则2个实体必须是单主键且生成策略必须是用户指定(assigned)。因为在保存时需要用户自行保证2个实体id的一致性。所以不能选用其它2种生成策略。如果想建立复合主键的单向1:1关联,可以使用单向N:1替代。
注意
- 如果要设置User的主键是通过 user.set("userId","1");
- 如果要设置UserInfo的主键是通过 userInfo.set("userId","1");
这和N:1的关联是不同的,因为N:1的关联,会把N端实体中用于关联的属性用1端实体代替,所以如果上面的例子改成User和UserInfo是N:1关联,则要设置User的主键就是通过user.set("userInfo/userId","1"),因为在N:1的情况下,N端实体里已经没有userId这个属性了。
单向1:1是没有延迟加载的,即当用户查询一个1端的实体,另一个被关联的1端实体也会被查询出来。 如com.eos.User和com.eos.UserInfo是单向1:1关联,则查询User时,会自动把UserInfo也查询出来。如果查询和更新在同一个IDASSession中,那么对User和UserInfo的修改都会反映到数据库中。
# 相关约束
- 2个实体必须是单主键且生成策略必须是用户指定(assigned);
- 用户保证2个实体主键的一致性;
- 单向1:1关联没有延迟加载,会通过一个左外联查询出主控对象和关联对象。
# 简单示例
//TODO:beginTransaction();
//TODO:getConnection();
//创建IDASSession,用于操作实体
IDASSession session = DASManager.createDasSession(connection);
//创建一个IDASCriteria,用于查询
IDASCriteria criteria = DASManager.createCriteria("com.eos.User");
//增加一个查询条件 User.userId = '0001'
criteria.add(ExpressionHelper.eq("userId","0001"));
//查询出一个User对象,并且关联的UserInfo也会被查出来
DataObject user = session.queryEntity(criteria);
//更新User及UserInfo
user.set ("userName","xiaohu2");
user.set("userInfo/phone","13310945445");
//保存更新, 这样对User和UserInfo的更新都会更新到数据库.
session.updateEntity(user);
session.close();
//TODO:closeConnection();
//TODO:commitTransaction();
这里之所以会产生类似级联操作的效果是因为查询和更新在同一个session里。
在session里会维护一个cache。在这个cache里会保存通过该session操作过的所有实体(包括查询和修改)。因此放在这个cache里的实体可以简单的理解为就是在数据库中的数据。当这个session和数据库进行同步时(如update),session会自动把cache里的所有单个实体和数据库进行一次同步。
注意
- 这种情况下session只会处理单个实体,对于实体集合会忽略。因为实体集合一定是属于某个实体的,而单个实体无法确认是否被其它实体引用还是就是一个孤立的实体。单向N:1也会有这种情况。
- 对于新增不存在该问题,因为新增时,实体还没有在session cache中。
# 单向N:1关联
# 简介
设置单向N:1关联的时候,需要在N端实体中指定用于关联的属性。该N端的属性可以是主键也可以是非主键。
1端的关联字段必须是主键(可以是单主键也可以是复合主键)。N端必须要选择相同个数的属性和1端主键匹配。 如:1端主键是单主键,那么N端也只要选择一个属性和它对应即可;如果1端主键是多主键,那么N端也要选择多个属性和它对应。
注意
当在N端选择了用于关联的属性,那么这些属性在N端实体中就会被删除,然后会用1个1端实体来代替。当要访问N端这些属性的值时,就需要通过访问相应的1端的主键值来获得。
单向N:1关联有3种不同方式的关联,以下的举例只是为了说明如何建立这3种不同的关联,并没有任何实际意义。
# 使用N端外键关联
这是一种普遍使用的场景,使用N端实体的非主键属性(orderId)和1端实体的主键属性(orderId)进行关联。
# 使用N端部分主键关联
N端实体是个联合主键(orderLineId,orderId),用了其中一个主键(orderId)和1端实体关联。
# 使用N端全部主键关联
可以把这个当作一种特殊的单向1:1关联,N端实体是个联合主键,并且所有主键都用于和1端实体进行关联。因为单向1:1关联不支持用联合主键做关联。
说明
如果想操作N端的用于关联的属性,则要通过操作1端实体的主键属性才可以。如oderLine.setString("order/oderId","0001"); orderLine.getString("order/oderId'); 因为N端用于关联的这个属性会在N端实体中被删除,所以无法操作。
单向N:1关联是支持延迟加载的。
- 如果设置延迟加载为false,那么会在查询N端实体时,同时查询1端实体,查询时是用一句join sql查询;
- 如果设为true,则先是查出所有的N端实体,然后根据N端的关联属性去查询1端实体。
# 相关约束
- 被关联的1端实体一定是主键,单/复合主键都可以。
- N端实体必须要选择相同个数的属性和1端主键匹配。 被选择的N端实体的属性或者全部是主键,或者全部是非主键。
- 单向N:1关联支持延迟加载。
# 单向1:N关联
# 简介
1端的关联字段必须是主键(可以是单主键也可以是复合主键)。N端必须要选择相同个数的列和1端主键匹配。
注意
- 单向1:N关联时的约束和单向N:1关联没有区别。 不同之处在于实体的使用方式。
- 单向N:1的关联,可以通过N端的实体对象访问到1端的实体对象,如orderLine.getString("order/orderDate");但是通过1端的实体对象无法访问到N端的实体对象,如order.getString("orderLines[1]/detail")。
- 单向1:N的关联,可以通过1端的实体对象访问到N端的实体对象,但是无法通过N端的实体对象访问到1端的实体对象。
单向1:N是支持延迟加载的,如果延迟加载设为false,那么会先用一句sql查出1端实体,然后根据1端实体的主键查询出关联的N端实体,这样就会产生1+N的sql查询。 如:
select * from ORDER
select * from ORDERLINE where ORDERID = '1'
select * from ORDERLINE where ORDERID = '2'
这样就是1+N的查询,会有3条sql。因为第一句sql查询出了2条1端记录,然后根据这2个1端实体的主键作为条件产生2句查询N端实体的sql。 数据量大的情况下会对性能造成影响。
如果延迟加载设为true,一开始只会生成第1句查询语句,后面2句不会生成,然后按照用户的需要指定要加载哪个1端实体关联的N端实体。
# 相关约束
- 1端实体一定是主键,单/复合主键都可以。
- 被关联的N端实体必须要选择相同个数的属性和1端主键匹配。 被选择的N端实体的属性或者全部是主键,或者全部是非主键。
- 单向1:N关联支持延迟加载。
# 关联相关API
EOS的数据服务模块暂时不支持级联操作,级联操作就是指对关联的主控方(即拥有关联属性的那个实体,如单向N:1中的OrderLine)进行新增/更新/删除操作时,被关联的对象也会跟着被新增/更新/删除。我们在这里主要讨论和查询相关的API。
示例代码中用到的实体定义如下:
# 动态的关联查询
对于关联实体是否使用延迟加载,除了可以通过创建关联时在Studio中指定,也可以通过API来动态的指定。 IDASCriteria的addAssociation就是为了完成该功能而提供的API。
- 对于单向N:1关联
//TODO:getConnection();
//创建IDASSession,用于操作实体
IDASSession session = DASManager.createDasSession(connection);
//创建一个IDASCriteria,用于查询
IDASCriteria criteria = DASManager.createCriteria("com.eos.OrderLine");
//增加一个关联查询
criteria.addAssociation("order");
List<DataObject> orderLines= session.query(criteria);
session.close();
//TODO:closeConnection();
以上代码会产生类似于下面的sql:
select * from ORDERLINE left outer join ORDER on ORDERLINE.ORDERID = ORDER.ORDERID
注意
EOS只提供了addAssociation,能把延迟加载变为立即加载,而没有提供API能把已经设置为立即加载的变为延迟加载;
使用该API会产生左外联的sql查询语句。
对于单向1:N关联
//TODO:getConnection();
//创建IDASSession,用于操作实体
IDASSession session = DASManager.createDasSession(connection);
//创建一个IDASCriteria,用于查询
IDASCriteria criteria = DASManager.createCriteria("com.eos.Order");
//增加一个关联查询
criteria.addAssociation("orderLines");
criteria.add(ExpressionHelper.eq("orderId","1"));
List<DataObject> orders= session.query(criteria);
session.close();
//TODO:closeConnection();
以上代码也会产生一个左外联的sql查询。如果ORDERID='1'的ORDER记录有3条ORDERLINE的关联记录,那么查询出来的结果就是3个Order对象,3个Order对象的地址(相当于java中==)是一样的,并且每个Order对象里都会有一个3个OrderLine对象的集合。这是因为sql查询出来的ResultSet会有3条记录,为了保证和ResultSet行数一样,所以对象也会产生3个。
注意
对于1:N的关联不建议使用左外联的查询,除非用户不关心查询出来的实体集合里有重复对象。
# 加载延迟的关联对象
对于已经在Studio里设置了延迟加载的关联,而又不想通过上面的API变为动态的关联查询,还可以通过IDASSession的expandRelation(DataObject entity,String property)这个API来查询一个对象的延迟加载的关联属性。参数entity是一个主键信息已经被设置好的数据对象,property是该数据对象中的一个关联属性,可以是1:N的关联属性也可以是N:1的关联属性。
对于延迟加载的单向1:N关联:
//TODO:getConnection();
//创建IDASSession,用于操作实体
IDASSession session = DASManager.createDasSession(connection);
//创建一个IDASCriteria,用于查询
IDASCriteria criteria = DASManager.createCriteria("com.eos.Order");
criteria.add(ExpressionHelper.eq("orderId","1"));
DataObject order= session.queryEntity (criteria);
//查询延迟加载的属性"orderLines"
session.expandRelation(order, "orderLines");
session.close();
//TODO:closeConnection();
单向N:1关联也可以使用该API进行查询,不再举例。
# 关联对象的属性做为查询条件
关联对象就是指有关联关系的对象。
对于使用关联对象的属性作为查询条件分为2种情况:
- 使用关联对象中用于关联的属性作为查询条件;
- 使用关联对象中其它的属性作为查询条件。
单向1:1、单向N:1,使用1端对象属性作为查询条件。
- 对于第一种情况代码示例如下:
//TODO:getConnection();
//创建IDASSession,用于操作实体
IDASSession session = DASManager.createDasSession(connection);
//创建一个IDASCriteria,用于查询
IDASCriteria criteria = DASManager.createCriteria("com.eos.OrderLine");
//增加一个关联对象的属性作为查询条件
criteria.add(ExpressionHelper.eq("order.orderId","1"));
List<DataObject> orderLines= session.query(criteria);
session.close();
//TODO:closeConnection();
criteria.add(ExpressionHelper.eq("order.orderId","1"));就是一个以关联对象中用于关联的属性作为查询条件的。因为OrderLine和Order的关联就是通过ORDERLINE.ORDERID和ORDER.ORDERID建立的。产生的sql类似于:
select * from ORDERLINE where ORDERID='1'
即一个单表查询。
说明
这里之所以生成的sql语句是对单表查询,是因为N端实体所对应的表里一定会有列和1端实体中用于关联的属性(即主键)所对应的列关联。 所以用1端实体的主键做为条件可以优化成使用N端的表里的关联列(可以理解成外键)作为查询条件。
注意
如果写了criteria.addAssociation("order");,再使用order.orderId做为查询条件,就不会再优化了,因为已经关联了Order,就会直接使用ORDER.ORDERID做为查询条件了。
- 对于第二种情况代码示例如下:
//TODO:getConnection();
//创建IDASSession,用于操作实体
IDASSession session = DASManager.createDasSession(connection);
//创建一个IDASCriteria,用于查询
IDASCriteria criteria = DASManager.createCriteria("com.eos.OrderLine");
java.util.Date orderDate = new Date();
//ORDER.ORDERDATE < new Date();
//增加一个关联对象的属性作为查询条件
criteria.addAssociation("order");
criteria.add(ExpressionHelper.lt("order.orderDate", orderDate));
List<DataObject> orderLines= session.query(criteria);
session.close();
//TODO:closeConnection();
以上代码产生的sql类似于:
select * from ORDERLINE left outer join ORDER on ORDERLINE.ORDERID = ORDER.ORDERID
where ORDER.ORDERDATE < new Date()
注意
和第一种情况不同的,第二种情况的查询需要先addAssociation("order"),然后才能使用Order对象里的属性orderDate作为查询条件。
对于单向1:N,无论是第一种情况还是第二种情况,都需要先addAssociation N端的实体对象,才能使用N端的属性作为查询条件。
# 实体类型映射关系
数据类型:是指在studio中的数据集编辑器中,创建实体属性时可以看到的下拉框中类型。
数据类型 | SDO类型 | DAS类型 | JAVA 类型 |
---|---|---|---|
Byte | Byte | byte | byte |
Short | Short | short | short |
Float | Float | float | float |
Int | Int | int | int |
BigInteger | Integer | big_integer | java.math.BigInteger |
Double | Double | double | double |
Long | Long | long | long |
Decimal | Decimal | big_decimal | java.math.BigDecimal |
Boolean | Boolean | boolean | boolean |
String | String | string | java.lang.String |
Date | Date | date | java.util.Date |
Time | Time | timeString | java.lang.String |
TimeStamp | Date | timestamp | java.util.Date |
ClobString | String | com.primeton.das.entity.impl.lob.type.ClobStringType | java.lang.String |
BlobByteArray | Bytes | com.primeton.das.entity.impl.lob.type.BlobByteArrayType | byte[] |
ClobFile | String | com.primeton.das.entity.impl.lob.type.ClobFileType | java.lang.String |
BlobFile | String | com.primeton.das.entity.impl.lob.type.BlobFileType | java.lang.String |
# 数据库操作类
具体介绍和使用说明参见数据库操作类