专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

SpringBoot集成SpringDataJPA项目实操

  前言
  该说的在《SpringBoot集成Mybatis项目实操》一文中都讲了,本文只是将Mybatis换成了SpringDataJPA,带大家将整个项目跑起来。
  本文将实现SpringBootSpringDataJPA的项目搭建,项目特色是针对JPA专门写了一套动态链式查询工具类,进而实现一对多查询和连表查询。
  不说废话了,我们直接进入主题。数据库
  本项目采用的是MySQL数据库,版本为8。x,建表语句如下:CREATETABLEcustomer(idint(11)NOTNULLAUTOINCREMENT,namevarchar(20)DEFAULTNULL,ageint(11)DEFAULTNULL,addressvarchar(100)DEFAULTNULL,createddatetimestampNULLDEFAULTNULL,lastmodifieddatetimestampNULLDEFAULTNULL,delflagint(2)NOTNULLDEFAULT0,createusernamevarchar(50)DEFAULTNULL,lastmodifiednamevarchar(50)DEFAULTNULL,versionint(11)NOTNULLDEFAULT0,PRIMARYKEY(id),UNIQUEKEYukname(name))ENGINEInnoDBDEFAULTCHARSETutf8COMMENT父用户;CREATETABLEsubUser(idint(11)NOTNULLAUTOINCREMENT,namevarchar(20)DEFAULTNULL,customeridvarchar(36)NOTNULL,addressvarchar(100)DEFAULTNULL,createddatetimestampNULLDEFAULTNULL,lastmodifieddatetimestampNULLDEFAULTNULL,delflagint(2)NOTNULLDEFAULT0,createusernamevarchar(50)DEFAULTNULL,lastmodifiednamevarchar(50)DEFAULTNULL,versionint(11)NOTNULLDEFAULT0,PRIMARYKEY(id))ENGINEInnoDBDEFAULTCHARSETutf8COMMENT子用户;复制代码搭建SpringBoot项目
  使用IDEA新建一个Maven项目,叫做jpaspringboot。
  一些共用的基础代码可以参考上篇文章,这里不做重复介绍,会介绍一些JPA相关的代码。引入依赖parentgroupIdorg。springframework。bootgroupIdspringbootstarterparentartifactIdversion2。6。3versionparentpropertiesjava。version1。8java。versionfastjson。version1。2。73fastjson。versionhutool。version5。5。1hutool。versionmysql。version8。0。19mysql。versionmybatis。version2。1。4mybatis。versionmapper。version4。1。5mapper。versionorg。mapstruct。version1。4。2。Finalorg。mapstruct。versionorg。projectlombok。version1。18。20org。projectlombok。versionpropertiesdependenciesdependencygroupIdorg。springframework。bootgroupIdspringbootstarterwebartifactIddependencydependencygroupIdorg。springframework。bootgroupIdspringbootstarteraopartifactIddependencydependencygroupIdorg。springframework。bootgroupIdspringbootstartersecurityartifactIddependencydependencygroupIdorg。springframework。bootgroupIdspringbootstartervalidationartifactIddependencydependencygroupIdcom。alibabagroupIdfastjsonartifactIdversion{fastjson。version}versiondependencydependencygroupIdcn。hutoolgroupIdhutoolallartifactIdversion{hutool。version}versiondependencydependencygroupIdorg。projectlombokgroupIdlombokartifactIdversion{org。projectlombok。version}versionoptionaltrueoptionaldependencydependencygroupIdorg。springframework。bootgroupIdspringbootstartertestartifactIdscopetestscopedependencydependencygroupIdmysqlgroupIdmysqlconnectorjavaartifactIdversion{mysql。version}versionscoperuntimescopedependencydependencygroupIdorg。mybatis。spring。bootgroupIdmybatisspringbootstarterartifactIdversion{mybatis。version}versiondependencydependencygroupIdorg。springframework。bootgroupIdspringbootstarterdatajpaartifactIddependencydependencygroupIdorg。springdocgroupIdspringdocopenapiuiartifactIdversion1。6。9versiondependencydependencygroupIdcom。alibabagroupIddruidspringbootstarterartifactIdversion1。1。18versiondependencydependencygroupIdorg。mapstructgroupIdmapstructartifactIdversion{org。mapstruct。version}versiondependencydependencygroupIdorg。mapstructgroupIdmapstructprocessorartifactIdversion{org。mapstruct。version}versiondependencydependenciesbuildpluginsplugingroupIdorg。springframework。bootgroupIdspringbootmavenpluginartifactIdpluginpluginsbuild复制代码
  有些依赖不一定是最新版本,而且你看到这篇文章时,可能已经发布了新版本,到时候可以先模仿着将项目跑起来后,再根据自己的需求来升级各项依赖,有问题咱再解决问题。分页处理
  某些业务场景是需要分页查询和排序功能的,所以我们需要考虑前端如何传递参数给后端,后端如何进行分页查询或者是排序查询。JPA分页查询使用的是Spring自带的Pageable。
  分页基础类publicclassSimplePageInfo{privateIntegerpageNum1;privateIntegerpageSize10;publicIntegergetPageNum(){returnpageNum;}publicvoidsetPageNum(IntegerpageNum){this。pageNumpageNum;}publicIntegergetPageSize(){returnpageSize;}publicvoidsetPageSize(IntegerpageSize){this。pageSizepageSize;}}复制代码
  排序包装类GetterSetterpublicclassOrderInfo{privatebooleanasctrue;privateStringcolumn;}复制代码
  分页且排序包装类GetterSetterEqualsAndHashCode(callSupertrue)publicclassPageSortInfoextendsSimplePageInfo{Schema(name排序信息)privateListOrderInfoorderInfos;publicStringparseSort(){if(CollectionUtils。isEmpty(orderInfos)){returnnull;}StringBuildersbnewStringBuilder();for(OrderInfoorderInfo:orderInfos){sb。append(orderInfo。getColumn())。append();sb。append(orderInfo。isAsc()?ASC,:DESC,);}sb。deleteCharAt(sb。length()1);returnsb。toString();}}复制代码
  前端分页查询的请求体对象GetterSetterAllArgsConstructorNoArgsConstructorBuilderpublicclassCustomerQueryPageDTO{JsonUnwrappedprivatePageSortInfopageSortInfo;}复制代码
  服务层分页查询PageablepageableSpecificationBuilder。getPageable(dto。getPageSortInfo());PageCustomercustomerPagecustomerRepository。findAll(pageable);复制代码
  关于PageHelper的使用这里就不多做介绍了。
  我们得到的分页查询结果是Page对象,可以直接使用,也可以根据需要进行修改,比如下面这个文件:GetterSetterpublicclassPageResultT{总条数privateLongtotal;总页数privateIntegerpageCount;每页数量privateIntegerpageSize;当前页码privateIntegerpageNum;分页数据privateListTdata;处理Jpa分页结果,Jpa页码默认从0开始,所以返回结果加1publicstaticTPageResultTok(org。springframework。data。domain。PageTpage){PageResultTresultnewPageResultT();result。setPageCount(page。getTotalPages());result。setPageNum(page。getNumber()1);result。setPageSize(page。getSize());result。setTotal(page。getTotalElements());result。setData(page。getContent());returnresult;}}复制代码JPA基础实体类
  作为其他实体类的父类,封装了所有的公共字段,包括逻辑删除标志,版本号,创建人和修改人信息。到底是否需要那么多字段,结合实际情况,这里的示例代码比较全,其中LogicDelete和Version是Mybatis特有的注解,CreatedBy、CreatedDate是Springframework自带的注解,如果我们需要新建人和修改人姓名,则需要自定义注解。MappedSuperclassEntityListeners(AuditingEntityListener。class)GetterEqualsAndHashCode(ofid)SuperBuilder(toBuildertrue)NoArgsConstructorAllArgsConstructorpublicclassBaseDomainimplementsSerializable{IdGeneratedValue(strategyGenerationType。IDENTITY)privateLongid;Schema(name创建人姓名)CreatedByColumn(namecreateusername)privateStringcreateUserName;CreatedDateprivateLocalDateTimecreatedDate;LastModifiedBySchema(name修改人姓名)Column(namelastmodifiedname)privateStringlastModifiedName;LastModifiedDateprivateLocalDateTimelastModifiedDate;Schema(name)Column(namedelflag)privateIntegerdelFlag0;Schema(name版本号)VersionColumn(nameversion)privateIntegerversion;}复制代码
  可以发现,相较于Mybatis和MybatisPlus少了两个字段,分别是createUserCode和lastModifiedCode,因为Spring提供的注解只有CreatedBy和LastModifiedBy,我们可以基于SpringData的AuditorAware审计功能来给这两个注解标识的字段赋值。JPA审计
  简单介绍一下审计功能:即由谁在什么时候创建或修改实体。SpringData提供了在实体类的属性上增加CreatedBy,LastModifiedBy,CreatedDate,LastModifiedDate注解,并配置相应的配置项,即可实现审计功能,有系统自动记录createdBy、CreatedDate、lastModifiedBy、lastModifiedDate四个属性的值。
  所以如果在AuditorAware实现类中根据CreatedBy拿到的值去数据库中查询userCode信息,也不是不可以,这里项目比较简单,所以就不过多介绍了。ConfigurationpublicclassJpaAutoConfigurationimplementsAuditorAwareString{OverridepublicOptionalStringgetCurrentAuditor(){SecurityContextctxSecurityContextHolder。getContext();Objectprincipalctx。getAuthentication()。getPrincipal();if(principal。getClass()。isAssignableFrom(String。class)){returnOptional。of((String)principal);}else{returnOptional。empty();}}}复制代码
  因为我们没有使用SpringSecurity来配置token信息,所以这里获取的principal是默认值,值为anonymousUser。动态链式查询
  利用JPA的Specification接口和元模型就实现动态查询,相较于这篇文章,本文实现借鉴了Mybatis中使用的ExampleBuilder,个人觉得效果更佳。
  1、查询关系匹配枚举publicenumMatchCondition{equal相等,notEqual不等于,like模糊匹配,notLike,gt大于,ge大于等于,lt小于,le小于等于EQUAL,NOTEQUAL,LIKE,NOTLIKE,GT,GE,LT,LE,IN,NOTIN,BETWEEN,NOTBETWEEN}复制代码
  2、查询条件连接符枚举publicenumOperator{AND,OR}复制代码
  3、查询条件注解Target({ElementType。FIELD,ElementType。CONSTRUCTOR})Retention(RetentionPolicy。RUNTIME)publicinterfaceQueryCondition{数据库中字段名,默认为空字符串,则Query类中的字段要与数据库中字段一致Stringcolumn()default;seeMatchConditionMatchConditionfunc()defaultMatchCondition。EQUAL;}复制代码
  4、连接符枚举publicenumConnector{ON,WHERE}复制代码
  5、查询条件包装类理论上会有多个QueryParam对象,当Connector是where时,joinName和joinType为null,当Connector为on时,根据joinName的不同,会生成不同的QueryParam对象,joinType包括INNER、LEFTDataNoArgsConstructorAllArgsConstructorBuilderpublicclassQueryParam{privateConnectorconnector;privateListQueryItemqueryItems;privateStringjoinName;privateJoinTypejoinType;}DataNoArgsConstructorAllArgsConstructorBuilderpublicclassQueryItem{privateStringfieldName;privateObjectfieldValue;privateMatchConditionmatchCondition;between使用privateObjectstartValue;privateObjectendValue;in查询privateIterableObjectiterable;privateOperatoroperator;}复制代码
  6、集成工具类
  因代码篇幅受限,就只粘贴部分代码:publicSpecificationBuilderTandEqualTo(IFnT,Objectfn,Objectvalue){QueryItemqueryItemQueryItem。builder()。fieldName(Reflections。fnToFieldName(fn))。fieldValue(value)。operator(Operator。AND)。matchCondition(MatchCondition。EQUAL)。build();addQueryItemToWhereParam(queryItem);returnthis;}privatevoidaddQueryItemToWhereParam(QueryItemqueryItem){if(CollectionUtils。isEmpty(queryParams)){queryParams。add(addQueryItem(queryItem));}else{OptionalQueryParamqueryParamOptionalqueryParams。stream()。filter(objStringUtils。isEmpty(obj。getJoinName()))。findFirst();if(queryParamOptional。isPresent()){QueryParamqueryParamqueryParamOptional。get();queryParam。getQueryItems()。add(queryItem);}else{queryParams。add(addQueryItem(queryItem));}}}复制代码
  相较于Mybatis项目中的ExampleBuilder,SpecificationBuilder仅用于查询功能,暂时无法提供修改,删除等功能,不过也有自己的额特色:可以实现连表查询,比如说leftjoin等,以及懒加载问题,避免连表查询时出现的N1查询。
  至此,关于本项目中有价值的内容已经讲述完毕,因篇幅有限,未能展示所有代码。基于上述核心代码,我们只需要往项目中添加相关业务代码即可,接下来我们就可以运行之前写的脚本工具,根据数据库表信息快速生成模板代码。一键式生成模版代码
  运行ormgenerate项目,在swagger上调用build接口,调用参数如下:{database:mysqldb,flat:true,type:jpa,group:hresh,host:127。0。0。1,module:orm,password:root,port:3306,table:〔customer,subuser〕,username:root,tableStartIndex:0}复制代码
  代码文件直接移到项目中就行了,稍微修改一下引用就好了。功能实现请求日志输出
  比如说我们访问customersqueryPage接口,看看控制台输出情况:RequestInfo:{classMethod:com。msdn。orm。hresh。controller。CustomerController。queryPage,ip:127。0。0。1,requestParams:{dto:{pageSortInfo:{count:true,pageSize:5,orderInfos:〔{column:name,asc:true}〕,pageNum:1}}},httpMethod:GET,url:http:localhost:8803customersqueryPage,result:{code:200,message:操作成功,success:true},methodDesc:获取父用户分页列表,timeCost:268}复制代码
  可以看到,日志输出中包含前端传来的请求体,请求API,返回结果,API描述,API耗时。统一返回格式
  比如说分页查询,返回结果如下:{data:{total:9,pageCount:2,pageSize:5,pageNum:1,data:〔{name:acorn,age:38,address:湖北武汉},{name:acorn2,age:28,address:湖北武汉},{name:hresh,age:44,address:湖北武汉},{name:love0,age:26,address:湖北武汉},{name:love1,age:26,address:湖北武汉}〕},code:200,message:操作成功,success:true}复制代码
  如果是新增请求,返回结果为:{data:{name:rookie3,age:26,address:湖北武汉},code:200,message:操作成功,success:true}复制代码异常处理
  下面简单演示一下参数异常的情况,在adduser时校验参数值是否为空。publicCustomerVOadd(CustomerDTOdto){if(StringUtils。isBlank(dto。getName())){BusinessException。validateFailed(userName不能为空);}CustomercustomercustomerRepository。save(customerStruct。dtoToModel(dto));returncustomerStruct。modelToVO(customer);}复制代码
  如果传递的name值为空,则返回结果为:{data:null,code:400,message:userName不能为空,success:false}复制代码分组校验和自定义校验
  修改CustomerDTO,当新增数据时,校验name不为空,修改数据时,address不为空。publicclassCustomerDTO{NotBlank(groups{Add。class})Schema(name)privateStringname;EnumValidator(value{18,20,24},messageage只能指定为18、20和24,其他值不合法)Schema(name)privateIntegerage;NotBlank(groups{Update。class})Schema(name)privateStringaddress;}复制代码
  最后修改controller文件PostMappingOperation(description新增父用户)publicResultCustomerVOadd(Validated(Add。class)RequestBodyCustomerDTOdto){CustomerVOcustomerVOcustomerService。add(dto);returnResult。ok(customerVO);}复制代码
  调用新增接口,故意将name置为空,返回结果为:{data:null,code:400,message:name不能为空,success:false}复制代码
  如果age不为18、20和24,则出现如下错误:{data:null,code:400,message:ageage只能指定为18、20和24,其他值不合法,success:false}复制代码批量操作
  这里简单演示一下关于批量新增的代码publicvoidbatchAdd(CustomerDTOdto){ListCustomercustomersnewArrayList();for(inti0;i3;i){CustomercustomernewCustomer();customer。setName(dto。getName()i);customer。setAge(dto。getAge());customer。setAddress(dto。getAddress());customers。add(customer);}customerRepository。saveAll(customers);}复制代码
  注意,delFlag没有对应的注解,所以只能手动赋值为0,否则插入数据时会报错。
  执行效果如下:
  分页查询
  前端参数传递:{pageNum:1,pageSize:5,orderInfos:〔{column:name,asc:true}〕}复制代码
  后端代码处理:publicPageCustomerVOqueryPage(CustomerQueryPageDTOdto){PageablepageableSpecificationBuilder。getPageable(dto。getPageSortInfo());PageCustomercustomerPagecustomerRepository。findAll(pageable);returncustomerPage。map(customercustomerStruct。modelToVO(customer));}复制代码
  返回结果为:{data:{total:9,pageCount:2,pageSize:5,pageNum:1,data:〔{name:acorn,age:38,address:湖北武汉},{name:acorn2,age:28,address:湖北武汉},{name:hresh,age:44,address:湖北武汉},{name:love0,age:26,address:湖北武汉},{name:love1,age:26,address:湖北武汉}〕},code:200,message:操作成功,success:true}复制代码动态查询
  查询方法如下:publicListCustomerVOqueryList(CustomerDTOdto){ListCustomercustomersSpecificationBuilder。create(CustomerRepository。class)。andLike(Customer::getName,dto。getName())。select();returncustomerStruct。modelToVO(customers);}复制代码
  执行结果如下:{data:〔{name:rookie,age:26,address:湖北武汉},{name:rookie1,age:26,address:湖北武汉},{name:rookie2,age:26,address:湖北武汉},{name:rookie3,age:26,address:湖北武汉}〕,code:200,message:操作成功,success:true}复制代码
  SQL输出为:selectcustomer0。idasid10,customer0。createusernameascreateu20,customer0。createddateascreated30,customer0。delflagasdelflag40,customer0。lastmodifieddateaslastmod50,customer0。lastmodifiednameaslastmod60,customer0。versionasversion70,customer0。addressasaddress80,customer0。ageasage90,customer0。nameasname100fromcustomercustomer0where(customer0。delflag0)and(customer0。namelike?)复制代码
  如果是分页查询,可以这样处理:publicPageCustomerVOqueryPage(CustomerQueryPageDTOdto){PageablepageableSpecificationBuilder。getPageable(dto。getPageSortInfo());PageCustomercustomerPageSpecificationBuilder。create(CustomerRepository。class)。andLike(Customer::getName,dto。getName())。select(pageable);PageCustomercustomerPagecustomerRepository。findAll(pageable);returncustomerPage。map(customercustomerStruct。modelToVO(customer));}复制代码
  查询结果为:{data:{total:4,pageCount:1,pageSize:5,pageNum:1,data:〔{name:rookie,age:26,address:湖北武汉},{name:rookie1,age:26,address:湖北武汉},{name:rookie2,age:26,address:湖北武汉},{name:rookie3,age:26,address:湖北武汉}〕},code:200,message:操作成功,success:true}复制代码
  SQL输出为:selectcustomer0。idasid10,customer0。createusernameascreateu20,customer0。createddateascreated30,customer0。delflagasdelflag40,customer0。lastmodifieddateaslastmod50,customer0。lastmodifiednameaslastmod60,customer0。versionasversion70,customer0。addressasaddress80,customer0。ageasage90,customer0。nameasname100fromcustomercustomer0where(customer0。delflag0)and(customer0。namelike?)orderbycasewhencustomer0。nameisnullthen1else0end,customer0。nameasclimit?复制代码一对多查询
  使用JPA的好处就是可以使用OneToMany等注解,以及懒加载查询优化注解EntityGraph。
  1、修改实体类publicclassCustomerextendsBaseDomain{privatestaticfinallongserialVersionUID1L;Schema(name)Column(namename)privateStringname;Schema(name)Column(nameage)privateIntegerage;Schema(name)Column(nameaddress)privateStringaddress;OneToMany(cascadeCascadeType。ALL)JoinColumn(namecustomerid)privateListSubUsersubUsers;}publicclassSubUserextendsBaseDomain{privatestaticfinallongserialVersionUID1L;Schema(name)Column(namename)privateStringname;Schema(name)Column(namecustomerid)privateStringcustomerId;Schema(name)Column(nameaddress)privateStringaddress;ManyToOneJoinColumnprivateCustomercustomer;}复制代码
  2、更改CustomerRepository,这点格外重要,只有如此,才能使得SpecificationBuilder更有意义,否则即使可以连表查询,也会出现N1问题。RepositorypublicinterfaceCustomerRepositoryextendsJpaRepositoryCustomer,Long,JpaSpecificationExecutorCustomer{OverrideEntityGraph(attributePaths{subUsers})ListCustomerfindAll(SpecificationCustomerspec);OverrideEntityGraph(attributePaths{subUsers})PageCustomerfindAll(SpecificationCustomerspec,Pageablepageable);OverrideEntityGraph(attributePaths{subUsers})ListCustomerfindAll(SpecificationCustomerspec,Sortsort);}复制代码
  3、Service层修改查询方法publicListCustomerVOqueryList(CustomerDTOdto){ListCustomercustomersSpecificationBuilder。create(CustomerRepository。class)。andLike(Customer::getName,dto。getName())。select();returncustomerStruct。modelToVO(customers);}复制代码
  4、查询结果为{data:〔{name:rookie,age:26,address:湖北武汉,subUserVOS:〔{name:a1,address:青藏高原},{name:a2,address:青藏高原}〕},{name:rookie1,age:26,address:湖北武汉,subUserVOS:〔{name:c1,address:黄土高原},{name:c2,address:黄土高原}〕},{name:rookie2,age:26,address:湖北武汉,subUserVOS:〔〕},{name:rookie3,age:26,address:湖北武汉,subUserVOS:〔〕}〕,code:200,message:操作成功,success:true}复制代码
  5、SQL输出
  如果没有CustomerRepository的重写方法,则会出现N1问题。
  连表查询
  除了上述查询方法的使用,我们还可以手动来增加leftjoin的查询条件,比如说我们连表查询时,还要在on查询上增加额外的条件,也可以通过SpecificationBuilder来实现。
  假设Customer和SubUser没有使用JoinColumn进行关联,而你此时想进行关联查询,可以这样做。publicListCustomerVOqueryList(CustomerDTOdto){ListCustomercustomersSpecificationBuilder。create(CustomerRepository。class)。andLike(Customer::getName,dto。getName())。leftJoin(Customer::getSubUsers)表示leftjoinsubuser。select();returncustomerStruct。modelToVO(customers);}复制代码
  对应的SQL如下:selectcustomer0。idasid100,subusers1。idasid111,customer0。createusernameascreateu200,customer0。createddateascreated300,customer0。delflagasdelflag400,customer0。lastmodifieddateaslastmod500,customer0。lastmodifiednameaslastmod600,customer0。versionasversion700,customer0。addressasaddress800,customer0。ageasage900,customer0。nameasname1000,subusers1。createusernameascreateu211,subusers1。createddateascreated311,subusers1。delflagasdelflag411,subusers1。lastmodifieddateaslastmod511,subusers1。lastmodifiednameaslastmod611,subusers1。versionasversion711,subusers1。addressasaddress811,subusers1。customeridascustome1011,subusers1。nameasname911,subusers1。customeridascustome1010,subusers1。idasid110fromcustomercustomer0leftouterjoinsubusersubusers1oncustomer0。idsubusers1。customeridwhere(customer0。delflag0)and(customer0。namelike?)复制代码
  如果你想额外增加on查询条件,可以这样实现。publicListCustomerVOqueryList(CustomerDTOdto){ListCustomercustomersSpecificationBuilder。create(CustomerRepository。class)。andLike(Customer::getName,dto。getName())。leftJoin(Customer::getSubUsers)。leftJoinAndOnEqualTo(Customer::getSubUsers,name,a1)。select();returncustomerStruct。modelToVO(customers);}复制代码
  对应的SQL为selectcustomer0。idasid100,subusers1。idasid111,customer0。createusernameascreateu200,customer0。createddateascreated300,customer0。delflagasdelflag400,customer0。lastmodifieddateaslastmod500,customer0。lastmodifiednameaslastmod600,customer0。versionasversion700,customer0。addressasaddress800,customer0。ageasage900,customer0。nameasname1000,subusers1。createusernameascreateu211,subusers1。createddateascreated311,subusers1。delflagasdelflag411,subusers1。lastmodifieddateaslastmod511,subusers1。lastmodifiednameaslastmod611,subusers1。versionasversion711,subusers1。addressasaddress811,subusers1。customeridascustome1011,subusers1。nameasname911,subusers1。customeridascustome1010,subusers1。idasid110fromcustomercustomer0leftouterjoinsubusersubusers1oncustomer0。idsubusers1。customeridand(subusers1。name?)where(customer0。delflag0)and(customer0。namelike?)复制代码
  关于on条件查询的实现,可以查看SpecificationBuilder中这段源码:ListQueryParamonQueryParamsqueryParams。stream()。filter(objStringUtils。isNotEmpty(obj。getJoinName()))。collect(Collectors。toList());for(QueryParamonQueryParam:onQueryParams){ListQueryItemqueryItemsonQueryParam。getQueryItems();if(CollectionUtils。isEmpty(queryItems)){root。join(onQueryParam。getJoinName(),onQueryParam。getJoinType());}else{JoinObject,Objectjoinroot。join(onQueryParam。getJoinName(),onQueryParam。getJoinType());for(QueryItemqueryItem:queryItems){ObjectvaluequeryItem。getFieldValue();switch(queryItem。getMatchCondition()){caseEQUAL:if(valueinstanceofString){关联表where查询andPredicates。add(criteriaBuilder。equal(join。get(queryItem。getFieldName())。as(String。class),value));关联表on查询join。on(criteriaBuilder。equal(join。get(queryItem。getFieldName())。as(String。class),value));}elseif(valueinstanceofInteger){andPredicates。add(criteriaBuilder。equal(join。get(queryItem。getFieldName())。as(Integer。class),value));join。on(criteriaBuilder。equal(join。get(queryItem。getFieldName())。as(Integer。class),value));}elseif(valueinstanceofLong){andPredicates。add(criteriaBuilder。equal(join。get(queryItem。getFieldName())。as(Long。class),value));on查询join。on(criteriaBuilder。equal(join。get(queryItem。getFieldName())。as(Long。class),value));}break;caseLIKE:if(valueinstanceofString){join。on(criteriaBuilder。like(join。get(queryItem。getFieldName())。as(String。class),value));}break;default:}}}}复制代码
  如果你感兴趣,可以取消注释代码,试一下将subUser。name查询放到where条件中。if(valueinstanceofString){关联表where查询andPredicates。add(criteriaBuilder。equal(join。get(queryItem。getFieldName())。as(String。class),value));关联表on查询join。on(criteriaBuilder。equal(join。get(queryItem。getFieldName())。as(String。class),value));}复制代码
  对应的SQL为:selectcustomer0。idasid100,subusers1。idasid111,customer0。createusernameascreateu200,customer0。createddateascreated300,customer0。delflagasdelflag400,customer0。lastmodifieddateaslastmod500,customer0。lastmodifiednameaslastmod600,customer0。versionasversion700,customer0。addressasaddress800,customer0。ageasage900,customer0。nameasname1000,subusers1。createusernameascreateu211,subusers1。createddateascreated311,subusers1。delflagasdelflag411,subusers1。lastmodifieddateaslastmod511,subusers1。lastmodifiednameaslastmod611,subusers1。versionasversion711,subusers1。addressasaddress811,subusers1。customeridascustome1011,subusers1。nameasname911,subusers1。customeridascustome1010,subusers1。idasid110fromcustomercustomer0leftouterjoinsubusersubusers1oncustomer0。idsubusers1。customeridwhere(customer0。delflag0)and(customer0。namelike?)andsubusers1。name?复制代码
  为什么要这样实现呢?首先我们要了解on和where的区别:连接查询中,on是用来确定两张表的关联关系,关联好之后生成一个临时表,之后where对这个临时表再进行过滤筛选。先执行on,后执行where;on是建立关联关系在生成临时表时候执行,where是在临时表生成后对数据进行筛选的。复制代码
  所以优先执行on条件查询,效率更高。
  不仅如此,上述SQL执行结果为:{data:〔{name:rookie,age:26,address:湖北武汉,subUserVOS:〔{name:a1,address:青藏高原}〕}〕,code:200,message:操作成功,success:true}复制代码
  这明显不是我们想要的结果。修改数据
  在本项目中,表结构中都包含了version这个字段,即每次更新操作,version都应该加1。
  CustomerRepository文件RepositorypublicinterfaceCustomerRepositoryextendsJpaRepositoryCustomer,Long,JpaSpecificationExecutorCustomer{ModifyingQuery(valueupdatecustomersetage:agewherename:nameanddelflag0,nativeQuerytrue)voidupdateByName(Param(name)Stringname,Param(age)intage);CustomerfindByName(Stringname);}复制代码
  Service修改方法TransactionalOverridepublicCustomerVOedit(CustomerDTOdto){通过自定义修改方法的方式来达到修改数据,先修改再查询,version没有改变,修改时间也不变customerRepository。updateByName(dto。getName(),dto。getAge());returncustomerStruct。modelToVO(customerRepository。findByName(dto。getName()));先查询,再修改,这种方式才会触发乐观锁,即where条件中有version条件,更新操作verison1,修改时间也会变化CustomercustomercustomerRepository。findByName(dto。getName());customer。setAge(dto。getAge());customerRepository。save(customer);returncustomerStruct。modelToVO(customer);}复制代码Swagger
  启动项目后,访问swagger,页面展示如下:
  总结
  上文中本人对于实现的SpecificationBuilder类还算满意,一开始只是为了实现分页查询,慢慢想实现MybatisPlus的那种链式查询,以及最后想要实现连接查询。代码实现目前就这样吧,如果大家有什么好玩的想法,欢迎大家留言评论。
  感兴趣的朋友可以去我的Github下载相关代码,如果对你有所帮助,不妨Star一下,谢谢大家支持!
  原文链接:https:juejin。cnpost7169472183184719886

大反攻!离岸人民币盘中反弹1200点,英国紧急出手救市!俄否认制造北溪管道泄漏事故,点名拜登回应俄乌冲突北溪管道泄漏事故英国央行紧急救市美元指数下跌离岸人民币暴拉1200基点昨夜今晨,全球不平静。美方建议在俄美国公民尽快离开欧盟委员会宣布第八轮对俄制裁措施当地时间9月28日,斯大林的一句话,为何让毛主席改变决定?将开国大典提前三个月众所周知,我国的国庆节是每年的10月1日,但是大家知道吗,当年新中国成立前夕,我国最初定的开国大典并非是1949年10月1日,而是1950年的1月1日,整整提前了3个月,而之所以会国家发改委煤价电价机制改革组合拳确保了电价稳定南都讯记者程姝雯发自北京今年9月英国法国德国市场交易电价涨至去年同期的410倍,而我国电力用户平均电价同比仅上涨10,何以实现?在今天(9月29日)国家发改委专题新闻发布会现场,发喝鲜榨果汁吃新鲜水果吗?水果中含有多种维生素矿物质及膳食纤维,不过喝鲜榨果汁吃新鲜水果,因为选择不同的吃法,机体所吸收的营养物质也不一样。那么,在榨汁过程中发生了什么呢?1果汁中维生素含量偏少在榨汁过程水天梭力洛克,一款实惠经典的手表一个半世纪以来,天梭横跨高级制表和日常使用领域。瑞士制表师于1853年以大规模生产怀表而闻名,此后成为富豪和普通民众的最爱。在猫王和格蕾丝凯利的手腕上发现了天梭表。安吉丽娜朱莉曾在新赛季值得关注的新双子星组合米切尔加兰ampampamp吹羊穆雷在列NBA训练营已经开启,展望新赛季,有很多东西值得球迷们期待。从这些方面出发,我们盘点了6队今年休赛期组建的很有趣的双子星组合。他们已经摩拳擦掌,预备从10月20号开始大展拳脚。1米去河南南阳必点四道经典名菜,是豫菜之魂老板以为你南阳人?聚焦河南美食。走进河南南阳品尝河南南阳的四道经典豫菜。一定能打开大家的味觉盛宴,河南南阳和洛阳是我国齐名的文化古都,也是河南著名的旅游文化城市。曾经三国时期的军事家诸葛亮就在河南南拉萨什么菜好吃拉萨10大经典名菜推荐拉萨是西藏的政治经济文化和宗教中心,作为首批中国历史文化名城,拉萨以风光秀丽历史悠久风俗民情独特宗教色彩浓厚而闻名于世。拉萨的美食名菜也颇为有名,拉萨美食以藏族风味为主,拉萨十大名国庆精品旅游线路推荐(一)畅游经典乐逗国宝的山水之旅2022年的国庆长假倒计时3天啦都江堰的游玩攻略,已经准备好啦畅游经典乐逗国宝的山水之旅第1天上午前往都江堰景区,观千年水利工程中午品尝古城区美食下午逛古城打卡明星网红同款自拍熊猫旗袍,中华女子的经典服装,哪些人适合穿?怎么穿才好看?夏天的暑热退去,深秋的凉意还没到来,现在正是穿旗袍最好的季节。旗袍,经久不衰的东方时尚旗袍是中华女性最具代表性的传统服装,历经百年而经久不衰。可以说,旗袍连接起生活和艺术,连接起过经典语录(一)我们也许不能够在一个约定的日子里驶达目的港,但我们总可以走在一条真正的航线上。梭罗人只要有出世的精神,才可以做入世的事业。朱光潜信念是鸟,它在黎明仍然黑暗之际,感觉到了光明,唱出了
1962年,一个外国人提着一把宝剑,来到国家博物馆说要捐献1962年,一个外国人提着一把宝剑,来到国家博物馆说要捐献,工作人员接过剑一看,顿时脸色大变,原来,剑身上竟然刻有李秀成三个字!01。外国人捐献宝剑1962年8月30日,东长安街,欧洲现存的微型苏联?仅有3个建交国,德河沿岸是国家吗?今天桃花石杂谈要从下面这面旗帜说起。相信很多朋友在看到这面旗的时候都会有种既熟悉又奇怪的感觉。说熟悉是因为这面旗的主体图案可以说就是苏联国旗,特别是左上角的那个镰刀锤子加金星的图案她是名将之后,皇帝的外孙女,历经八朝,差点成为武则天她是公主之女,皇帝的外孙女,嫁给表侄为妻,靠子孙成为五朝太后,尊荣数十年,但结局凄凉,在唐朝身居后位的女性并不在少数,但除了长孙皇后武则天和韦皇后外,其他人的知名度在历史上则要差了ST凯乐,虚增营业收入500多亿,A股的假究竟还有多大可造?12月3日,ST凯乐(湖北凯乐科技股份有限公司)发布关于收到中国证券监督管理委员会行政处罚和市场禁入事先告知书的公告,2022年12月2日,公司收到中国证券监督管理委员会行政处罚和大城市取消限购,三四线城市房价可能撑不住面对着楼市的低迷,利好楼市的政策频出,说是史诗级的救市虽然有点夸张,但是政策出台如此密集,支持房企的力度如此之大也实属罕见,也是无奈之举,毕竟稳字当头。目前来看除了房贷利率下调是向从古代的领约项牌朝珠聊起,解读古人为何喜爱佩戴另类饰品?首饰的设计与制作是人类有史以来创造美的一项重大实践活动,是人类文明发展到一定阶段的产物。人类在长期的社会生活当中,不仅习惯以首饰作为情感寄托,而且以其因有的物质价值和优美属性来评估古人玄怪轶事驱鼠术唐朝末年,僖宗皇帝在位,广陵(今江苏扬州)有个穷汉子,叫杜可筠,年纪有个四十余岁,喜好喝酒不怎么吃饭,好多人都说他一粒米都不吃。杜可筠经常去酒馆,挨着酒桌一桌一桌的讨酒喝,也没见他九天阊阖开宫殿,万国衣冠拜冕旒,一文带你了解宏伟的唐长安城盛唐时期的唐帝国毫无疑问是当时世界上最强盛的国家,作为文化政治及经济中心的京城长安更是当时规模最庞大经济最繁荣建筑最宏伟文化最多元的国际化大都市,那么唐长安城到底有多宏伟?今天咱们意外!作为足球王国的巴西,超8成的球员收入达不到工资最低标准北京时间12月6日凌晨,卡塔尔世界杯一场18决赛在卡塔尔的974球场上演,最终巴西上半场35分钟打进4球,全场41轻松地击败了韩国队,昂首挺进8强,这也是桑巴军团连续8届挺进世界杯比香港还小的省会,只有一个县城大小,却深受年轻人的欢迎国内大部分的省会城市其实对于国人来说都是比较熟悉的存在,而且大部分人也都有机会能够去到这些省会城市游玩一番,大部分的省会城市本身应该是属于该地区经济较为发达的城市之一。甚至还有可能探访福建沙县全息机器人餐厅图为福建沙县全息机器人餐厅,工作人员正在取机器人炒的蛋炒饭。张斌摄图为福建沙县全息机器人餐厅,工作人员正在为顾客上机器人制作的炒蛋。张斌摄图为福建沙县全息机器人餐厅,工作人员在准备
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网