Skip to content

数据库操作

zhangxin edited this page Mar 6, 2017 · 13 revisions

目录

介绍

ZStack采用HibernateJPA进行数据库操作,整个框架架设在Spring Data框架之上。

早期ZStack代码使用原生的JPA EntityManager(多表查询、多语句执行)和SimpleQuery(基于JPA EntityManager封装的类,用于单表查询)。两种方法都比较verbose,在新版本中我们使用QSQL以及DatabaseFacade三个类统一所有数据库操作。本文介绍在各种场景下,它们的使用方法。

原生的JPA EntityManager和SimpleQuery不应该再被直接使用

基本的CRUD操作

所有对于单表的CRUD操作通过DatabaseFacade完成

创建操作(Create)

创建一条记录:

使用DatabaseFacade.persist()操作,例如:

ImageCacheVolumeRefVO ref = new ImageCacheVolumeRefVO();
ref.setImageCacheId(cache.getId());
ref.setPrimaryStorageUuid(self.getUuid());
ref.setVolumeUuid(vol.getUuid());
dbf.persist(ref);

创建一条记录并更新:

使用DatabaseFacade.persistAndRefresh()操作插入一条新记录,并从数据库读入以填充由数据库自动生成的字段,例如createData、lastOpDate等。例如:

final ImageVO vo = new ImageVO();
vo.setUuid(msg.getResourceUuid() == null ? Platform.getUuid() : msg.getResourceUuid());
vo.setName(msg.getName());
vo.setDescription(msg.getDescription());
vo.setType(ImageConstant.ZSTACK_IMAGE_TYPE);
vo.setMediaType(ImageMediaType.DataVolumeTemplate);
vo.setSize(size);
vo.setActualSize(actualSize);
vo.setState(ImageState.Enabled);
vo.setStatus(ImageStatus.Creating);
vo.setFormat(format);
vo.setUrl(String.format("volume://%s", msg.getVolumeUuid()));
image = dbf.persistAndRefresh(vo);

创建多条记录:

使用DatabaseFacade.persistCollection()操作,例如:

for (String url : msg.getMonUrls()) {
    CephBackupStorageMonVO monvo = new CephBackupStorageMonVO();
    MonUri uri = new MonUri(url);
    monvo.setUuid(Platform.getUuid());
    monvo.setStatus(MonStatus.Connecting);
    monvo.setHostname(uri.getHostname());
    monvo.setMonAddr(monvo.getHostname());
    monvo.setMonPort(uri.getMonPort());
    monvo.setSshPort(uri.getSshPort());
    monvo.setSshUsername(uri.getSshUsername());
    monvo.setSshPassword(uri.getSshPassword());
    monvo.setBackupStorageUuid(self.getUuid());
    monVOs.add(monvo);
}

dbf.persistCollection(monVOs);

所有数据会在同一个Trasancation内插入。

读入记录(Read)

使用UUID读入一条记录:

对于使用UUID作为primary key的数据库表,使用DatabaseFacade.findByUuid()读入一条记录。例如:

 ApplianceVmVO apvm = dbf.findByUuid(spec.getVmInventory().getUuid(), ApplianceVmVO.class);

使用ID读入一条记录:

对于使用Long类型自动增加的id作为primary key的数据库表,使用DatabaseFacade.findById()读入一条记录。例如:

ImageCacheVO c = dbf.findById(id, ImageCacheVO.class);

重新读入一条记录:

对于一个JPA Entity对象,可以使用DatabaseFacade.reload()重新从数据库读入该对象。例如:

EipVO vo = dbf.findByUuid(some_uuid, EipVO.class);
....
//做了某些操作后,重新载入
vo = dbf.reload(vo);

更新记录(Update)

更新一条记录:

使用DatabaseFacade.update()操作,例如:

ApplianceVmVO apvm = dbf.findByUuid(spec.getVmInventory().getUuid(), ApplianceVmVO.class);
apvm.setManagementNetworkUuid(mgmtNic.getL3NetworkUuid());
dbf.update(apvm);

更新并记录并更新:

使用DatabaseFacade.updateAndRefresh()更新一条记录,并重新从数据库中读入。例如:

image.setMd5Sum(mdsum);
image.setStatus(ImageStatus.Ready);
image = dbf.updateAndRefresh(image);

更新多条记录:

使用DatabaseFacade.updateCollection()更新多条记录,更新操作在同一个transaction中执行。例如:

List<Object> refs = new ArrayList<>();
for (ImageBackupStorageRefVO ref : self.getBackupStorageRefs()) {
    if (toRecoverBsUuids.contains(ref.getBackupStorageUuid())) {
        ref.setStatus(ImageStatus.Ready);
        refs.add(ref);
    }
}

self.setStatus(ImageStatus.Ready);
refs.add(self);
dbf.updateCollection(refs);

删除记录(Delete)

删除一条记录

使用DatabaseFacade.delete()删除一条记录。例如:

VipVO self = dbf.findByUuid(vip_uuid, VipVO.class);
dbf.remove(self);

删除多条记录

使用DatabaseFacade.removeCollection()删除同种类型的多条记录,在同一transaction种。例如:

dbf.removeCollection(getSelf().getMons(), CephBackupStorageMonVO.class);

使用Q进行单表查询操作

配合JPA的metamodel(代码中所有以VO_结尾的class),我们可以方便的使用Q进行单表查询,并获得Java的编译时检查,达到type-safe的目的。

Q包含如下查询条件函数:

条件函数 描述 用例
eq() 等于 eq(HostVO_.uuid, some_uuid)
notEq() 不等于 notEq(HostVO_.uuid, some_uuid)
in() 属于集合 in(HostVO_.uuid, asList(uuid1, uuid2, uui3))
notIn() 不属于某个集合 notIn(HostVO_.uuid, asList(uuid1, uuid2, uuid3))
isNull() 值为null isNull(HostVO_.managementIp)
notNull() 值不为null notNull(HostVO_.managementIp)
gt() 大于 gt(VmInstanceVO_.cpu, 8)
gte() 大于等于 gte(VmIntanceVO_.cpu, 8)
lt() 小于 lt(VmInstanceVO_.cpu, 8)
lte() 小于等于 lte(VmInstanceVO_.cpu, 8)
like() 模糊匹配字符串 like(HostVO_.name, "%web-server%")
notLike() 不包含模糊匹配的字符串 notLike(HostVO_.name, "%web-server%")
orderBy() 以某字段排序 orderBy(VmInstanceVO_.cpu, SimpleQuery.Od.ASC)
groupBy() 以某字段分组 groupBy(VmInstanceVo.cpu
limit() 最大返回记录数 limit(1000)
start() 指定从哪条记录开始查询,等同于SQL中的offset关键字 start(100)

Q包含如下操作函数:

操作函数 描述 返回值
select() 选择要查询的一个或多个字段
isExist() 满足条件的记录是否存在 boolean, true/false
count() 满足条件的记录数 long,记录数
find() 满足条件的整条记录。不能和select()函数连用 代表记录的JPA entity对象,例如HostVO
list() 满足条件的一组记录。不能和select()函数连用 代表记录的一组JPA entity对象
findValue() 满足条件的某个记录的当个字段值,该字段被select()函数选中,例如select(HostVO_.name) 记录的字段值
listValues() 满足条件的一组记录的单个字段,该字段被select()函数选中,例如select(HostVO_.name) 包含一组记录某字段值的List
findTuple() 满足条件的某个记录的多个字段值,字段被select()函数选中,例如select(HostVO_.name,HostVO_.uuid) 包含多个字段值的JPA Tuple对象
listTuple() 满足条件的一组记录的多个字段,字段被select()函数选中,例如select(HostVO_.name,HostVO_.uuid) 包含一组记录多个字段值的List

查询多条记录

List<VmInstanceVO> vms = Q.New(VmInstanceVO.class)
    .gt(VmInstanceVO_.cpuNum, 4)
    .notEq(VmInstanceVO_.name, "web").list();

查询单条记录

VmInstanceVO vm = Q.New(VmInstanceVO.class)
    .eq(VmInstanceVO_.name, "web").find();

选择一个字段,查询多条记录

List<String> vmNames = Q.New(VmInstanceVO.class)
    .select(VmInstanceVO_.uuid).gt(VmInstanceVO_.cpuNum, 4).listValues();

选择一个字段,查询一条记录

String vmName = Q.New(VmInstanceVO.class).select(VmInstanceVO_.uuid)
    .eq(VmInstanceVO_.name, "web").findValue();

选择多个字段,查询多条记录

List<Tuple> tuples = Q.New(VmInstanceVO.class)
    .select(VmInstanceVO_.uuid, VmInstanceVO_.name)
    .gt(VmInstanceVO_.cpuNum, 4).listTuple();

选择多个字段,查询一条记录

Tuple tuple = Q.New(VmInstanceVO.class)
    .select(VmInstanceVO_.uuid, VmInstanceVO_.name)
    .eq(VmInstanceVO_.name, "web").findTuple();

String uuid = tuple.get(0, String.class);
String name = tuple.get(1, String.class);

使用SQL类执行单个JPQL语句

当我们要执行一个复杂的查询,例如多表join、sub query时,我们需要使用SQL类。SQL类同时可以用来对单表进行修改操作,使用JPA metamodel实现type-safe。

SQL类使用JPQL语言(JPA的类SQL语言)执行查询,通过param()函数指定查询条件,list()返回一组记录,find()返回单个记录,execute()直接执行一个非select SQL语句(例如update或delete)。

跟Q不同的是,SQL只有list()和find()函数,没有细分的listValues()、listTuple()、findValue()、findTuple()等函数。可以使用SQL.New(String sql, Class returnClass)来指定返回值的类型。例如如果sql使用select选定了多个字段,则可以用:

List<Tuple> tuples = SQL.New("select vm.name, vm.uuid from VmInstanceVO vm", Tuple.class).list();

如果select只选定了一个字段,则可以用:

List<String> vmNames = SQL.New("select vm.name from VmInstanceVO vm", String.class).list();

下面看更多的一些例子:

String providerType = SQL.New("select v.serviceProvider from VipVO v, EipVO e where e.vipUuid = v.uuid" +
        " and e.uuid = :euuid").param("euuid", eipUuid).find();

List<String> l3Uuids = SQL.New("select l3.uuid" +
        " from L3NetworkVO l3, VipVO vip, NetworkServiceL3NetworkRefVO ref, NetworkServiceProviderVO np" +
        " where l3.system = :system" +
        " and l3.uuid != vip.l3NetworkUuid" +
        " and l3.uuid = ref.l3NetworkUuid" +
        " and ref.networkServiceType = :nsType" +
        " and l3.zoneUuid = :zoneUuid" +
        " and vip.uuid = :vipUuid" +
        " and np.uuid = ref.networkServiceProviderUuid" +
        " and np.type = :npType")
        .param("system", false)
        .param("zoneUuid", zoneUuid)
        .param("nsType", EipConstant.EIP_NETWORK_SERVICE_TYPE)
        .param("npType", providerType)
        .param("vipUuid", vipUuid)
        .list();

Tuple result = SQL.New(
        "select sum(hc.availableCpu), sum(hc.availableMemory), vm.cpuNum, vm.memorySize" +
        " from HostCapacityVO hc, HostVO host, VmInstanceVO vm" +
        " where hc.uuid = host.uuid" +
        " and host.state = :hstate" +
        " and host.status = :hstatus", Tuple.class
).param("hstate", HostState.Enabled).param("hstatus", HostStatus.Connected).find();

可以用offset()limit()函数指定起始查询位置(相当于offset关键字)以及最大返回记录数(相当于limit关键字):

List<VmNicVO> nics  = SQL.New("select nic" +
        " from VmNicVO nic, VmInstanceVO vm" +
        " where nic.l3NetworkUuid in (:l3Uuids)" +
        " and nic.vmInstanceUuid = vm.uuid" +
        " and vm.type = :vmType and vm.state in (:vmStates) " +
        // IP = null means the VM is just recovered without any IP allocated
        " and nic.ip is not null")
        .param("l3Uuids", l3Uuids)
        .param("vmType", VmInstanceConstant.USER_VM_TYPE)
        .param("vmStates", Arrays.asList(VmInstanceState.Running, VmInstanceState.Stopped))
        .offset(10) // 起始位置为10
        .limit(100) // 返回最多100条记录
        .list();

可以使用execute()函数执行delete或update操作:

SQL.New("delete from VirtualRouterEipRefVO ref where ref.eipUuid = :eipUuid")
        .param("eipUuid", struct.getEip().getUuid()).execute();

SQL.New("update VmIntanceVO vm set vm.name = :name where vm.uuid = :uuid")
        .param("name", "newName").param("uuid", "36c27e8ff05c4780bf6d2fa65700f22e").execute();

上面的例子中,对于单表的delete和update操作都直接使用了JPQL语句,我们也可以通过使用JPA metamodel实现type-safe,让编译器帮忙检查语法错误。将上面两个例子metamodel方式改写为:

SQL.New(VirtualRouterEipRefVO.class).eq(VirtualRouterEipRefVO_.eipUuid, struct.getEip().getUuid()).delete();

SQL.New(VmIntanceVO.class).set(VmIntanceVO_.name, "newname").eq(VmIntanceVO_.uuid, "36c27e8ff05c4780bf6d2fa65700f22e").update();

上例中我们用了eq()函数,前面Q类支持的所有条件函数,例如eq(), like(), lt()等。

使用SQLBatch类执行多个JPQL语句

使用SQL类我们只能执行单个JPQL语句,要在一个transaction内执行多个JPQL语句,我们需要使用SQLBatch类。SQLBatch是SQL类的一个简单封装,用户通过sql()函数指定多个JPQL语句,SQLBatch将他们组合在一起,在一个transaction中执行。

sql()函数实际返回的是一个SQL类,所有SQL类的方法都可以使用。

用一个例子就可以展示SQLBatch的所有使用方式:

new SQLBatch() {
    // delete the image if it's not on any backup storage
    @Override
    protected void scripts() {
        long count = sql("select count(ref) from ImageBackupStorageRefVO ref" +
                " where ref.imageUuid = :uuid", Long.class)
                .param("uuid", msg.getImageUuid()).find();

        if (count == 0) {
            // the image is expunged on all backup storage
            sql("delete from ImageVO img where img.uuid = :uuid")
                    .param("uuid", msg.getImageUuid()).execute();

            logger.debug(String.format("the image[uuid:%s, name:%s] has been expunged on all backup storage, remove it from database", self.getUuid(), self.getName()));
        }
    }
}.execute();

再来看一个复杂一点的例子:

new SQLBatch() {
    @Override
    protected void scripts() {
        List<LocalStorageResourceRefVO> refs = sql(
                "select ref from LocalStorageResourceRefVO ref where ref.hostUuid = :huuid" +
                        " and ref.primaryStorageUuid = :psUuid", LocalStorageResourceRefVO.class
        ).param("huuid", hostUuid).param("psUuid", self.getUuid()).list();

        if (refs.isEmpty()) {
            return;
        }

        List<String> volumesUuids = new ArrayList<>();
        List<String> snapshotUuids = new ArrayList<>();
        for (LocalStorageResourceRefVO ref : refs) {
            if (VolumeVO.class.getSimpleName().equals(ref.getResourceType())) {
                volumesUuids.add(ref.getResourceUuid());
            } else if (VolumeSnapshotVO.class.getSimpleName().equals(ref.getResourceType())) {
                snapshotUuids.add(ref.getResourceUuid());
            }
        }

        if (!snapshotUuids.isEmpty()) {
            sql("delete from VolumeSnapshotVO sp where sp.uuid in (:uuids)")
                    .param("uuids", snapshotUuids).execute();

            logger.debug(String.format("delete volume snapshots%s because the host[uuid:%s] is removed from" +
                    " the local storage[name:%s, uuid:%s]", snapshotUuids, hostUuid, self.getName(), self.getUuid()));
        }

        if (!volumesUuids.isEmpty()) {
            List<String> vmUuidsToDelete = sql("select vm.uuid from VmInstanceVO vm where vm.rootVolumeUuid in" +
                    " (select vol.uuid from VolumeVO vol where vol.uuid in (:volUuids)" +
                    " and vol.type = :volType)", String.class)
                    .param("volUuids", volumesUuids).param("volType", VolumeType.Root).list();


            if (!vmUuidsToDelete.isEmpty()) {
                // delete vm nics
                sql("delete from VmNicVO nic where nic.vmInstanceUuid in (:uuids)")
                        .param("uuids", vmUuidsToDelete).execute();
            }

            // delete volumes including root and data volumes
            sql("delete from VolumeVO vol where vol.uuid in (:uuids)")
                    .param("uuids", volumesUuids).execute();
            logger.debug(String.format("delete volumes%s because the host[uuid:%s] is removed from" +
                    " the local storage[name:%s, uuid:%s]", volumesUuids, hostUuid, self.getName(), self.getUuid()));

            if (!vmUuidsToDelete.isEmpty()) {
                // delete the vms
                sql("delete from VmInstanceVO vm where vm.uuid in (:uuids)")
                        .param("uuids", vmUuidsToDelete).execute();

                logger.debug(String.format("delete VMs%s because the host[uuid:%s] is removed from" +
                        " the local storage[name:%s, uuid:%s]", vmUuidsToDelete, hostUuid, self.getName(), self.getUuid()));
            }
        }

        // delete the image cache
        sql("delete from ImageCacheVO ic where ic.primaryStorageUuid = :psUuid and" +
                " ic.installUrl like :url").param("psUuid", self.getUuid())
                .param("url", String.format("%%%s%%", hostUuid)).execute();

        for (LocalStorageResourceRefVO ref : refs) {
            dbf.getEntityManager().merge(ref);
            dbf.getEntityManager().remove(ref);
        }
    }
}.execute();