SpringBoot与GraphQL集成
目录 GraphQL介绍 Spring Boot与Graphql集成
运行环境 :JDK 8,Maven 3.0+
技术栈 :SpringBoot 2.5+ 一、GraphQL 介绍
GraphQL既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。
GraphQL优势 请求你所要的数据,不多不少
向你的 API 发出一个GraphQL 请求就能准确获得你想要的数据,不多不少。 GraphQL 查询总是返回可预测的结果。使用 GraphQL 的应用可以工作得又快又稳,因为控制数据的是应用,而不是服务器。 获取多个资源只用一个请求
GraphQL 查询不仅能够获得资源的属性,还能沿着资源间引用进一步查询。典型的 REST API 请求多个资源时得载入多个 URL,而 GraphQL 可以通过一次请求就获取你应用所需的所有数据。这样一来,即使是比较慢的移动网络连接下,使用 GraphQL 的应用也能表现得足够迅速。 描述所有的可能类型系统
GraphQL API 基于类型和字段的方式进行组织,而非入口端点。你可以通过一个单一入口端点得到你所有的数据能力。GraphQL 使用类型来保证应用只请求可能的数据,还提供了清晰的辅助性错误信息。应用可以使用类型,而避免编写手动解析代码。 Spring Boot与 GraphQL集成Graphql插件Idea开发工具集成Graphql插件,从File ->settings –>Plugins安装插件
安装完插件后,可创建graphql类型文件
数据库及表创建数据库创建:
create database sopbase; 表创建:
create table sys_user
(
user_id bigint auto_increment
primary key ,
username varchar ( 50 ) not null comment "用户名" ,
password varchar ( 100 ) null comment "密码" ,
salt varchar ( 20 ) null comment "盐" ,
email varchar ( 100 ) null comment "邮箱" ,
mobile varchar ( 100 ) null comment "手机号" ,
status tinyint null comment "状态 0:禁用 1:正常" ,
dept_id bigint null comment "部门ID" ,
create_time datetime null comment "创建时间" ,
constraint username
unique (username)
)
comment "系统用户" charset = utf8mb4; 项目工程结构及源码介绍项目工程结构
核心源码介绍API层提供查询及更改类解析器,分别实现查询和更改接口
/**
* 用户查询类解析器
*
* @author lxj
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
@Component
public class UserQueryResolver implements GraphQLQueryResolver {
@Autowired
private UserService userService ;
public List getUserList() {
return userService .list();
}
public User getUserInfo(Long userId) {
return userService .getById(userId);
}
public PageUtils getUserPage(Map params){
PageUtils page = userService .queryPage(params);
return page;
}
}
/**
* 用户更新类解析器
*
* @author lxj
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
@Component
public class UserMutationResolver implements GraphQLQueryResolver, GraphQLMutationResolver {
@Autowired
private UserService userService ;
public R addUser(User user) {
userService .stroe(user);
return R. ok ();
}
public R deleteUser(Integer userId) {
if (userId == null ){
return R. error (- 1 , "fail" );
}
userService .removeById(userId);
return R. ok ();
}
public R updateUser(User user) {
if (user == null ){
return R. error (- 1 , "fail" );
}
userService .updateById(user);
return R. ok ();
}
} dao – 数据操作层 DAO
@Mapper
//表明这是一个Mapper,也可以在启动类上加上包扫描
//Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
public interface UserMapper extends BaseMapper {
} entity – 实体类
@Data
@TableName (value = "sys_user" )
@ApiModel (description = "用户信息" )
public class User implements Serializable {
private static final long serialVersionUID = - 5644799954031156649L ;
//value与数据库主键列名一致,若实体类属性名与表主键列名一致可省略value
@TableId (value = "user_id" , type = IdType. AUTO ) //指定自增策略
@ApiModelProperty (value = "用户ID" ,required= true )
private Long userId ;
/**
* 用户名
*/
@ApiModelProperty (value = "用户名" ,required= true )
private String username ;
/**
* 密码
*/
@ApiModelProperty (value = "用户密码" ,required= true )
private String password ;
/**
* 盐
*/
private String salt ;
/**
* 邮箱
*/
@ApiModelProperty (value = "用户邮箱" ,required= true )
private String email ;
/**
* 手机号
*/
private String mobile ;
/**
* 状态 0:禁用 1:正常
*/
private Integer status ;
/**
* 部门ID
*/
private Long deptId ;
/**
* 部门名称
*/
@TableField (exist= false )
private String deptName ;
/**
* 角色ID列表
*/
@TableField (exist= false )
private List roleIdList ;
/**
* 创建时间
*/
@TableField (fill= FieldFill. INSERT_UPDATE )
private Date createTime ;
} service – 业务逻辑层
接口类:
public interface UserService extends IService {
/**
* 分页查询
* @param params
* @return
*/
PageUtils queryPage(Map params);
/**
* 根据姓名查询
* @param name
* @return
*/
User queryByName(String name);
boolean stroe(User user);
void update(User user);
User getUserById(Long userId);
}
实现类:
@Service ( "userService" )
public class UserServiceImpl extends ServiceImpl implements UserService {
@Override
public PageUtils queryPage(Map params) {
String name = (String)params.get( "username" );
QueryWrapper userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.like(StringUtils. isNotEmpty (name), "username" ,name);
Query query = new Query<>();
IPage page= this .page(query.getPage(params),userQueryWrapper);
return new PageUtils(page);
}
@Override
public User queryByName(String name) {
QueryWrapper userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq( "username" ,name);
return this .getOne(userQueryWrapper);
}
@Override
@Transactional (rollbackFor = Exception. class )
public boolean stroe(User user) {
String salt = RandomStringUtils. randomAlphanumeric ( 20 );
String pwd = user.getPassword()+salt;
user.setSalt(salt);
user.setPassword(DigestUtils. md5Hex (pwd));
this .save(user);
return true ;
}
@Override
@Transactional (rollbackFor = Exception. class )
public void update(User user) {
if (org.apache.commons.lang.StringUtils. isBlank (user.getPassword())){
user.setPassword( null );
} else {
String salt = RandomStringUtils. randomAlphanumeric ( 20 );
String pwd = user.getPassword()+salt;
user.setSalt(salt);
user.setPassword(DigestUtils. md5Hex (pwd));
}
this .updateById(user);
}
@Override
public User getUserById(Long userId) {
User user = this .getById(userId);
return user;
}
} Application – 应用启动类
/**
* 启动类
*
* @author lxj
*/
@SpringBootApplication
public class LearnGraphqlApplication {
public static void main(String[] args) {
SpringApplication. run (LearnGraphqlApplication. class , args);
}
} Mapper配置
<? xml version ="1.0" encoding ="UTF-8" ?>
mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
< mapper namespace ="com.learn.springboot.dao.UserMapper" >
< resultMap type ="com.learn.springboot.entity.User" id ="userMap" >
< result property ="userId" column ="user_id" />
< result property ="username" column ="username" />
< result property ="password" column ="password" />
< result property ="salt" column ="salt" />
< result property ="email" column ="email" />
< result property ="mobile" column ="mobile" />
< result property ="status" column ="status" />
< result property ="deptId" column ="dept_id" />
< result property ="createTime" column ="create_time" />
resultMap >
mapper > application.yml – 应用配置文件,应用启动会自动读取配置
server :
port : 80
servlet :
context-path : /
tomcat :
# tomcat的URI编码
uri-encoding : UTF-8
# tomcat最大线程数,默认为200
max-threads : 800
# Tomcat启动初始化的线程数,默认值25
min-spare-threads : 30
spring :
datasource :
type : com.alibaba.druid.pool.DruidDataSource
druid :
driver-class-name : com.mysql.cj.jdbc.Driver
username : sopbase
password : sopbase
url : jdbc:mysql://localhost:3306/sopbase?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
initial-size : 10 #连接池初始大小
max-active : 100 #连接池中最大的活跃连接数
min-idle : 10 #连接池中最小的活跃连接数
max-wait : 60000 #配置获取连接等待超时的时间
pool-prepared-statements : true #打开PSCache,并且指定每个连接上PSCache的大小
max-pool-prepared-statement-per-connection-size : 20
time-between-eviction-runs-millis : 60000
min-evictable-idle-time-millis : 300000
#Oracle需要打开注释
#validation-query: SELECT 1 FROM DUAL
test-while-idle : true #是否在连接空闲一段时间后检测其可用性
test-on-borrow : false #是否在获得连接后检测其可用性
test-on-return : false #是否在连接放回连接池后检测其可用性
stat-view-servlet :
enabled : true
url-pattern : /druid/*
#login-username: admin
#login-password: admin
filter :
stat :
log-slow-sql : true
slow-sql-millis : 1000
merge-sql : false
wall :
config :
multi-statement-allow : true
#mybatis
mybatis-plus :
mapper-locations : classpath*:/mapper/**/*.xml
#实体扫描,多个package用逗号或者分号分隔 com.example.*.entity
typeAliasesPackage : com.learn.springboot.entity
global-config :
#数据库相关配置
db-config :
#主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID", ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
id-type : AUTO
logic-delete-value : -1
logic-not-delete-value : 0
banner : false
#原生配置
configuration :
#开启sql日志
log-impl : org.apache.ibatis.logging.stdout.StdOutImpl
#log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
#log-impl: org.apache.ibatis.logging.log4j2.Log4j2Impl
# 该配置就是将带有下划线的表字段映射为驼峰格式的实体类属性
map-underscore-to-camel-case : true
cache-enabled : false
call-setters-on-nulls : true
jdbc-type-for-null : " null "
#GraphQL com.graphql-java-kickstart
graphql :
servlet :
mapping : /graphql
enabled : true
corsEnabled : true # 关闭跨域,仅使用浏览器插件调试时设置为false
playground :
cdn :
enabled : true # playground 使用 cdn 的静态文件
# if you want to @ExceptionHandler annotation for custom GraphQLErrors
exception-handlers-enabled : true
contextSetting : PER_REQUEST_WITH_INSTRUMENTATION
tools :
#扫描 resource 下 .graphql 后缀的文件
schema-location-pattern : "**/*.graphql" GraphQL文件配置
result.graphql
type R{
code : Int!
msg : String
}
root.graphql
#定义查询类型和更改类型
schema {
query : Query
mutation : Mutation
}
# 定义一个空的查询类型
type Query{
}
# 定义一个空的更改类型
type Mutation{
}
user.graphql
extend type Query {
getUserInfo (userId: String!): User
getUserList : [User]
getUserPage (params : paginationUserInput!): PageUserResult!
}
extend type Mutation {
addUser ( user: addUserInput!): R
deleteUser (userId: String!): R
updateUser (user:updateUserInput): R
}
type User {
#
userId : Long
#用户名
username : String
#密码
password : String
#盐
salt : String
#邮箱
email : String
#手机号
mobile : String
#状态 0:禁用 1:正常
status : Int
#部门ID
deptId : Int
#createTime:Date
}
type PageUserResult {
currPage : Int!
pageSize : Int!
totalPage : Int!
totalCount : Int!
list : [User]!
}
input paginationUserInput {
limit : String = "10"
page : String = "0"
#查询条件生成不固定
#用户名
username : String
#密码
password : String
#盐
salt : String
#邮箱
email : String
#手机号
mobile : String
#状态 0:禁用 1:正常
status : Int
#部门ID
deptId : Int
}
# 添加系统用户输入参数
input addUserInput {
#用户名
username : String
#密码
password : String
#盐
# salt: String
#邮箱
email : String
#手机号
mobile : String
#状态 0:禁用 1:正常
status : Int
#部门ID
deptId : Int
}
# 更新系统用户输入参数
input updateUserInput {
#
userId : String
#用户名
username : String
#密码
password : String
#盐
# salt: String
#邮箱
email : String
#手机号
mobile : String
#状态 0:禁用 1:正常
status : Int
#部门ID
deptId : Int
}
通常是一个对象就是一个 java 实体类,在 graphql 中也如此,也是一个对象对应一个 .graphql 文件。 项目配置说明项目pom.xml添加graphql依赖
< dependency >
< groupId >com.graphql-java-kickstartgroupId >
< artifactId >graphql-spring-boot-starterartifactId >
< version >11.0.0version >
dependency >
< dependency >
< groupId >com.graphql-java-kickstartgroupId >
< artifactId >graphiql-spring-boot-starterartifactId >
< version >8.1.1version >
< scope >runtimescope >
dependency > application.yml中graphql配置
#GraphQL
graphql :
servlet :
mapping : /graphql
enabled : true
corsEnabled : true # 关闭跨域,仅使用浏览器插件调试时设置为false
playground :
cdn :
enabled : true # playground 使用 cdn 的静态文件
# if you want to @ExceptionHandler annotation for custom GraphQLErrors
exception-handlers-enabled : true
contextSetting : PER_REQUEST_WITH_INSTRUMENTATION
tools :
#扫描 resource 下 .graphql 后缀的文件
schema-location-pattern : "**/*.graphql" 加载Mybatis配置
MapperScan 注解加载扫描持久层包路径,来增加配置。该配置也可以加在工程application启动类。无自定配置,只需通过持久层接口加@Mapper注解,可不引用@MapperScan注解。
/**
* MybatisPlus插件加载
*
* @author lxj
*/
@Configuration
@MapperScan ( "com.learn.springboot.dao" )
public class MybatisPlusConfig {
/**
* 新的分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor( new PaginationInnerInterceptor(DbType. MYSQL ));
return interceptor;
}
}
数据表字段自动填充实现
/**
* 定义写数据入库默认值
*
* @author lxj
*/
@Component
public class CTMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this .setFieldValByName( "createTime" , new Date(),metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
//自定元数据处理逻辑
}
} 自定义graphql类型
/**
* 定义graphql新Long类型
*
* @author lxj
*/
@Configuration
public class LongScalarConfig {
@Bean
public GraphQLScalarType longScalar() {
return GraphQLScalarType. newScalar ()
.name( "Long" )
.description( "Java 8 Long as scalar." )
.coercing( new Coercing() {
@Override
public String serialize( final Object dataFetcherResult) {
if (dataFetcherResult instanceof Long) {
return dataFetcherResult.toString();
} else {
throw new CoercingSerializeException( "Expected a Long object." );
}
}
@Override
public Long parseValue( final Object input) {
try {
if (input instanceof String) {
return new Long((String) input);
} else {
throw new CoercingParseValueException( "Expected a String" );
}
} catch (Exception e) {
throw new CoercingParseValueException(String. format ( "Not a valid Long: "%s"." , input), e
);
}
}
@Override
public Long parseLiteral( final Object input) {
if (input instanceof StringValue) {
try {
return new Long(((StringValue) input).getValue());
} catch (Exception e) {
throw new CoercingParseLiteralException(e);
}
} else {
throw new CoercingParseLiteralException( "Expected a StringValue." );
}
}
}).build();
}
} 工程演示右键运行 Application 应用启动类的 main 函数,然后在浏览器访问图形化界面:
http://localhost/graphiql
新增数据调用
查询调用:
注意:type Query 方法名和我们 resolver 中的方法名必须一致graphql 文件中定义的对象的属性列,必须在 java 实体类中一一进行对应
新年穿红更喜庆!双十二提前BUY爱慕内衣在中国传统文化中,凤凰与梧桐常常用于表达吉祥祝福的意象凤凰于飞,梧桐花开喜从天降,福运成双。爱慕将传统纹样融入现代设计理念,呈现不一样的新年红内衣,冬季特别推出好事将近系列,匠心打
2022年,会员店陷阱这是灵兽第1247篇原创文章业内零售企业,如果商超大卖场都做不好,就一定能做好会员店吗?作者晴山IDlingshouke2022年,零售企业对仓储会员店业态的青睐未减。这一年,山姆
A股今日下跌!但缺口仍未回补,后市怎么走?给大家4点提醒!1短线仍有继续横盘的需求上周大盘在缺口上方再形成缺口,让不少人认为本周起码要回补3170附近的缺口。这也是下午大盘在此缺口附近反复摩擦的原因。对此我从上周五至今反复强调,这种缺口叠
铜价稳居高位近期形势一片大好,外围股市大涨,美国股市在美联储加息势头大幅减弱的情况下涨了一波又一波,欧洲股市也不甘落后,也是齐刷刷上涨行情,我们的股市更是在打赢3000点保卫战后屡创新高,现在
央行11月份社会融资规模增量为1。99万亿元12月12日,中国人民银行发布消息,初步统计,2022年11月份社会融资规模增量为1。99万亿元,比上年同期少6109亿元。其中,对实体经济发放的人民币贷款增加1。14万亿元,同比
2022最值得买的洁面TOP3,芙丽芳丝菲诗小铺均上榜今年双11以来,洁面赛道竞争异常激烈,而Kbeauty上演了一场逆袭之战。就在近日,平价韩妆品牌菲诗小铺THEFACESHOP引发购买热潮,跃居天猫各大榜单,还获得了千万级美妆头部
所有的遇见,都是一种偿还!头条创作挑战赛你让世上所有情话都若有所指。你让我散落天涯的碎片都重归于己。爱情里没有谁对谁错,有的只是为了一个人而奋不顾身,即使得不到也为爱疯狂一回,潇洒一次,小编整理了一些关于爱
我与他,浮生一遇,便抵万千(完)多年以后,那油灯随风而灭,忽隐忽明间我又仿佛看到了他,他一身青衫立在船头,彬彬有礼的向我诚邀一曲。他与我或许都是可怜之人,我一介商女身世如浮萍不由自己,他一介官家踌躇满志却不得志。
善良的人该懂的黑暗人性不要以某种方式定义自己的好,这其实在变相定义别人的坏!商场如战场,永远不要把你的仁义道德陈述于你领导之前,从某种程度上来说,这就是拿领导的卑鄙来夸耀你自己的美德,如果你背景强大,实
一生执念,爱你无限头条创作挑战赛一生执念爱你无限心中万千的情愫皆因爱你而起像因花谢而生的忧伤像因落雪而生的感慨明知人生会有遗憾但仍然忍不住期盼期盼你能陪伴在我身边期盼好梦能重圆期盼我们相爱如初期盼我
Jupiter丘比特Pana绘景产品总监董清晓先生专访近日,JupiterPana绘景视觉协作空间携手合作伙伴,在深圳开启混合办公全场景视觉协作会议解决方案巡展第一站。Jupiter丘比特以219看世界新赛道共启程为主题,带来完整的全
莫迪访问尼泊尔,特地绕开中国承建的机场,外媒对尼影响力减弱当地时间5月16日,印度总理莫迪访问了尼泊尔,在佛教圣地蓝毗尼与尼泊尔总理德乌帕举行了会晤。值得注意的是,当天由中国承建的高塔姆佛祖机场正式运营,然而莫迪却非常刻意地绕过了这个新机
河南老人生前养蟒蛇赶鸭子当帮手用,老人去世后蛇顺着河逃走害人给大家分享一个真实的案例,就发生在离我们村不远的另外一个村庄,因为这个事,家里人小时候都不让我们去河边玩。那时我们那边的一个村庄,一个老人是养鸭子的,养了很多鸭子,老伴死后他靠自己
言情小说(乡村里的俏美妇)第七章张倩以为铁蛋又是找她的呢?结果失算了,人家铁蛋走她跟前都没停顿,直接就骑着自行车过去了。张倩此时红着脸有些尴尬,自己想太多了。太敏感了。张倩也是好奇,这铁蛋那么慌张地去干嘛的呢?想
杰伦直播吧5月18日讯NBA季后赛东决G1,凯尔特人客场不敌热火,总比分01落后。赛后,杰伦布朗接受采访时表示很显然,我们完全没有准备好如何在霍福德和斯玛特都缺阵的情况下打球。这不是输
全省汛期地质灾害防范工作督导组来涵督导5月16日上午,以省煤田地质局党委书记林杰为组长的全省2022年汛期地质灾害防范工作督导组来涵督导地质灾害防范工作,市自然资源局副局长黄常青区政府副区长郑碧娥及区相关单位参加督导工
上海居委会工作人员夜间值守吃瑞士卷却遭到居民的质疑和抵制致敬平凡的你上海市徐汇区徐泾镇玉兰清苑居委会工作人员,夜间在办公室值守吃瑞士卷。被小区居民拍摄下来,上传到网络上,却遭到小区居民的集体质疑。居委会给小区居民发的是方便面,居委会工作
湖南电信三千兆升级提质高标准赋能数字生活电信三千兆定义高标准活动于世界电信日当天发布。红网时刻新闻5月17日讯(记者向婉视频刘玮琦)以5月17日世界电信日为契机,中国电信湖南公司启动了电信三千兆定义高标准发布活动,对三千
2021年度贵州省广播电视节目评审结果公示日前,2021年度贵州省广播电视节目评审结束。经评审委员会评审,364件作品获评2021年度贵州省广播电视优秀节目,现予以公示。公示时间从2021年5月17日至5月23日。公示期内
明朝时陕西一富豪富可敌国,比肩沈万三,可是结局比和珅还惨大家都知道明朝时沈万三那可是响当当的有钱人,可谓是富可敌国。今天就给大家再聊一位明朝的钱人,实力可以和沈万三相媲美,不过这一位可不是什么商人,而是一个宦官。刘瑾,陕西兴平人,明朝正
成龙在安徽有两个亲哥哥家境贫寒,却自称不需要成龙帮扶2008年2月26日,成龙的父亲房道龙在香港因病逝世,享年93岁。一个星期后,房道龙的葬礼在澳大利亚堪培拉市举行,虽然是在异国他乡,但是葬礼规模却十分盛大,除了由成龙亲自主持以外,
圆明园四大兽首来南京了在第46个518国际博物馆日到来之际,南京城墙博物馆恢复开放。盛世聚首圆明园兽首特展也在该馆拉开帷幕。在南京城墙博物馆5月17日晚举办的博物馆奇妙夜活动中,来自圆明园西洋楼海晏堂的