jcrygier / graphql-jpa Goto Github PK
View Code? Open in Web Editor NEWJPA Implementation of GraphQL (builds on graphql-java)
License: MIT License
JPA Implementation of GraphQL (builds on graphql-java)
License: MIT License
As far as I was able to see, there is no easy way to introduce new scalar types into your handler. Currently, the method seems to be limited to statically coded in the list of types, as follows:
private GraphQLType getAttributeType(Attribute attribute) {
if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.BASIC) {
if (String.class.isAssignableFrom(attribute.getJavaType()))
return Scalars.GraphQLString;
else if (UUID.class.isAssignableFrom(attribute.getJavaType()))
return JavaScalars.GraphQLUUID;
else if (Integer.class.isAssignableFrom(attribute.getJavaType()) || int.class.isAssignableFrom(attribute.getJavaType()))
return Scalars.GraphQLInt;
else if (Short.class.isAssignableFrom(attribute.getJavaType()) || short.class.isAssignableFrom(attribute.getJavaType()))
return Scalars.GraphQLShort;
else if (Float.class.isAssignableFrom(attribute.getJavaType()) || float.class.isAssignableFrom(attribute.getJavaType())
|| Double.class.isAssignableFrom(attribute.getJavaType()) || double.class.isAssignableFrom(attribute.getJavaType()))
return Scalars.GraphQLFloat;
else if (Long.class.isAssignableFrom(attribute.getJavaType()) || long.class.isAssignableFrom(attribute.getJavaType()))
return Scalars.GraphQLLong;
else if (Boolean.class.isAssignableFrom(attribute.getJavaType()) || boolean.class.isAssignableFrom(attribute.getJavaType()))
return Scalars.GraphQLBoolean;
else if (Date.class.isAssignableFrom(attribute.getJavaType()))
return JavaScalars.GraphQLDate;
else if (LocalDateTime.class.isAssignableFrom(attribute.getJavaType()))
return JavaScalars.GraphQLLocalDateTime;
else if (LocalDate.class.isAssignableFrom(attribute.getJavaType()))
return JavaScalars.GraphQLLocalDate;
else if (attribute.getJavaType().isEnum()) {
return getTypeFromJavaType(attribute.getJavaType());
} else if (BigDecimal.class.isAssignableFrom(attribute.getJavaType())) {
return Scalars.GraphQLBigDecimal;
}
}
//[...]
}
Please, do correct me if I am wrogn here and misunderstood something.
It would be beneficial to be able to register this way or another new type for which there is an implementation of GraphQLScalarType
made. We had several (to say the least) such types in our systems, without them we can't use this library at the current stage of development.
I am happy to help if you will let me, but I would like to know what kind of implementation you would like to see here? We can do it in several different ways IMHO:
GraphQLScalarType
automatically - it will require a bit of work, but it's doable nevertheless. It would be way easier if this would be a Spring library, not a general library...I think a combination of 2. followed by 1. per framework makes most of sense here...
Could you please publish a new release to Bintray? It would be helpful if the fixes since the last release (such as BigDecimal support) would be packaged in an artifact.
Add support for @Embedded
and @Embeddable
http://docs.oracle.com/javaee/6/api/javax/persistence/Embedded.html
Please clarify the license for this project. There is no LICENSE file in the root of the repository.
The only hint I've found was at bintray where it says the license is MIT.
This is also the license that graphql-java uses.
However, it would be nice to make the license clear and easy to find.
How can i get the jar file of this project. I think its not in maven repository and also i'm facing some issue while using this also
<dependency>
<groupId>com.crygier</groupId>
<artifactId>graphql-jpa</artifactId>
<version>0.3</version>
<type>pom</type>
</dependency>
First, congratulations for your project and thanks for your open source contribution!
We have rewritten your test cases in pure Java - just keeping junit dependency - to help us track some corner issues. Please let me know if you are interested by a pull request. Maybe it could help the project since Groovy can be scary for some developpers and not everyone want to add Spring to their stack.
When I try to use this schema
{ User(id:1){ name } }
I receive multiple exceptions.
Hi,
I was just wondering if you have some basic documentation about this. Interested in using it but just wondering if its the right fit.
I have an element collection of 'Class' which is used to persist the class name of an object. When the GraphQLSchemaBuilder hits this attribute, it checks to see if it's an enum (which it isn't) and if not, returns null.
This then causes a null to be passed into the constructor for GraphQLList.
Am I doing something wrong, or is this a bug?
Hello, and thanks for your amazing work.
I tried graphql-jpa on very simple projects and it worked simply, out-of-the-box, which is really neat !
Something I'm concerned about though, is lazy loading.
GraphQL is very good for the client to tell "I want that piece of information, and only that", which, translated into the ORM-world could mean : "I want that entity, this relation to that other entity, but not this other one".
What would be awesome, would be to add an ability, when creating the GraphQLQuery
, to generate an "adaptative" query, using FETCH JOIN
to fetch all the entities needed at once, avoiding lazy loading when sending the response to the client.
Not sure how to implement it, but by parsing the client's query (and the field he is asking for), and analyzing the annotations on the fields of the entity returned, I guess it should be possible.
What do you think ? Is this something you're thinking about ? Working on ? Is it too difficult ? Out of scope ?
Thanks a lot !
All many-to-one and one-to-one Mappings are eagerly fetched.
This removes all results, when the many-to-one/one-to-one mappings has no data.
So this query, will have no result:
def query = '''
{
Human(id: "1004") {
name
homePlanet
favoriteDroid {
name
}
}
}
'''
But I think, the correct result should be
Human: [
[name:'Wilhuff Tarkin', homePlanet:null, favoriteDroid:null]
]
At the moment the whole Schema is generated under the hood by the library without any way of editing it as developer. This way it's not possible to have mutations.
The reason for this is in GraphQLSchemaBuilder
.getGraphQLSchema()`:
public GraphQLSchema getGraphQLSchema() {
GraphQLSchema.Builder schemaBuilder = GraphQLSchema.newSchema();
schemaBuilder.query(getQueryType());
return schemaBuilder.build();
}
This method is invoked by GraphQLExecutor.createGraphQL()
:
@PostConstruct
protected void createGraphQL() {
if (entityManager != null)
this.graphQL = new GraphQL(new GraphQLSchemaBuilder(entityManager).getGraphQLSchema());
}
For testing I've changed this locally so that the GraphQLSchemaBuilder
now also gets a "schemaEnhancer" function which can be used to "enhance" the schema. The function has the signature BiConsumer<GraphQLSchema.Builder, EntityManager>
:
public GraphQLSchemaBuilder(EntityManager entityManager, BiConsumer<GraphQLSchema.Builder, EntityManager> schemaEnhancer) {
this.entityManager = entityManager;
this.schemaEnhancer = schemaEnhancer;
}
public GraphQLSchema getGraphQLSchema() {
GraphQLSchema.Builder schemaBuilder = GraphQLSchema.newSchema();
schemaBuilder.query(getQueryType());
if(schemaEnhancer != null) {
schemaEnhancer.accept(schemaBuilder, entityManager);
}
return schemaBuilder.build();
}
This way I can provide a function to manipulate the schema after the query type is generated but before the actual schema is build. This way I can add mutations like this:
public class Mutation {
public void addMutation(GraphQLSchema.Builder schemaBuilder, EntityManager entityManager) {
GraphQLObjectType mutation = newObject()
.name("Mutation")
.field(createMyMutation(entityManager))
.build();
schemaBuilder.mutation(mutation);
}
private GraphQLFieldDefinition createMyMutation(EntityManager entityManager) {
return newFieldDefinition()
.name("myMutation")
.type(new GraphQLTypeReference("Person"))
.argument(newArgument()
.name("name")
.type(new GraphQLNonNull(GraphQLString))
.build())
.dataFetcher(env -> {
String name = env.getArgument("name");
Person person = new Person(name);
return entityManager.merge(person);
}).build();
}
}
// Spring Boot
@Configuration
public class DiSetup {
@Bean
public GraphQLExecutor graphQLExecutor(EntityManager entityManager, Mutation mutation) {
return new GraphQLExecutor(entityManager, mutation::addMutation);
}
}
I need to pass through the enhancer function to the GraphQLExecutor
and from there to the GraphQLSchemaBuilder
. In my test project I'm using Spring Boot and therefore have to configure the dependency injection accordingly.
It works for me and I would be happy to provide a PullRequest. However, I'd like to discuss the approach beforehand because I'm not sure if this approach would work for environments other then Spring Boot and I'm not sure if the approach of passing an enhancer function is the best possible way. Maybe there is a better way that better uses dependency injection mechanisms?
EDIT:
Another aspect is that there may be use cases where the developer likes to add or manipulate the query part of the Schema. At the moment this isn't possible and it wouldn't be possible with my proposes solution either because the creation of the query part of the Schema is hard coded. Maybe we could find a more flexible way of defining these kinds of things.
Hi,
I had problems to use the @Embedded Annotation with the previous solution from:
#26
This solution does not "flatten" the attributes, instead it keeps the "normal" java model hierarchy.
So @embeddable Annotations behave like @manytoone and @OnetoOne Annotations.
The query looks like that:
Spaceship (id: "1000"){ name, created { user {id}}, modified {date} }
where "created" and "modified" are of Typ DateAndUser, which is a @embeddable Class
@Embeddable public class DateAndUser { public Date date; @ManyToOne public User user; }
When I try to pass an id argument of type Long
to execute
I get a ValidationError("Variable type doesn't match")
.
Debugging results in the following state:
inputType = GraphQLScalarType{name='Long', description='Long type', coercing=graphql.Scalars$2@25dc2c0}
variableType = GraphQLNonNull{wrappedType=GraphQLScalarType{name='Long', description='Built-in Long', coercing=org.crygier.graphql.JavaScalars$1@192ecf8}}
How can I update graphl-jpa to handle the Long id argument correctly?
Currently the entire graphql-jpa
has to be on the package of your domain EJB/JAR. It would be convenient to ship the annotation classes in a separate module.
Currently appears to support Date, but not GregorianCalendar.
Model attributes of java type GregorianCalendar throw UnsupportedOperationException (GraphQLSchemaBuilder, line 135).
Attempts to upgrade to v3.0.0 lead to the following (from @timtebeek):
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
...
Caused by: graphql.AssertException: All types within a GraphQL schema must have unique names. No two provided types may have the same name.
No provided type may have a name which conflicts with any built in types (including Scalar and Introspection types).
You have redefined the type 'Episode' from being a 'GraphQLEnumType' to a 'GraphQLEnumType'
at graphql.schema.SchemaUtil.assertTypeUniqueness(SchemaUtil.java:86)
at graphql.schema.SchemaUtil.collectTypes(SchemaUtil.java:50)
at graphql.schema.SchemaUtil.collectTypes(SchemaUtil.java:48)
at graphql.schema.SchemaUtil.collectTypesForObjects(SchemaUtil.java:130)
at graphql.schema.SchemaUtil.collectTypes(SchemaUtil.java:56)
at graphql.schema.SchemaUtil.allTypes(SchemaUtil.java:153)
at graphql.schema.GraphQLSchema.<init>(GraphQLSchema.java:42)
at graphql.schema.GraphQLSchema$Builder.build(GraphQLSchema.java:130)
at graphql.schema.GraphQLSchema$Builder.build(GraphQLSchema.java:125)
at org.crygier.graphql.GraphQLSchemaBuilder.getGraphQLSchema(GraphQLSchemaBuilder.java:37)
at org.crygier.graphql.GraphQLExecutor.createGraphQL(GraphQLExecutor.java:27)
...
Caused by repeated invocations of this function for org.crygier.graphql.model.starwars.Episode:
https://github.com/jcrygier/graphql-jpa/blob/master/src/main/java/org/crygier/graphql/GraphQLSchemaBuilder.java#L218
Usage would be infinitely easier if the artifact was uploaded to Maven Central. Any chance of this happening?
An exception is thrown if an entity in the JPA model contains an unsupported data type, eg a byteArray:
java.lang.UnsupportedOperationException: Attribute could not be mapped to GraphQL: field 'data' of entity class 'org.crygier.graphql.model.starwars.Droid'
It is possible to suppress this exception by adding @GraphQLIgnore
to the offending field. However this does not work if the entity is referenced by another entity in the model.
Issue can be duplicated by adding
@GraphQLIgnore byte[] data;
to the Droid model; all the test cases fail when trying to build the schema.
Given the following query:
{
HumanConnection {
totalElements
}
}
My expectation would be that I'm getting the number of elements for "Human" but actually an exception is thrown because the query doesn't contain the "content" field.
The exception is:
java.util.NoSuchElementException: No value present
at java.util.Optional.get(Optional.java:135) ~[na:1.8.0_121]
at org.crygier.graphql.ExtendedJpaDataFetcher.get(ExtendedJpaDataFetcher.java:45) ~[graphql-jpa-0.2.jar:na]
at graphql.execution.ExecutionStrategy.resolveField(ExecutionStrategy.java:40) ~[graphql-java-2.2.0.jar:na]
at graphql.execution.SimpleExecutionStrategy.execute(SimpleExecutionStrategy.java:18) [graphql-java-2.2.0.jar:na]
at graphql.execution.Execution.executeOperation(Execution.java:60) [graphql-java-2.2.0.jar:na]
at graphql.execution.Execution.execute(Execution.java:33) [graphql-java-2.2.0.jar:na]
at graphql.GraphQL.execute(GraphQL.java:78) [graphql-java-2.2.0.jar:na]
at graphql.GraphQL.execute(GraphQL.java:55) [graphql-java-2.2.0.jar:na]
at graphql.GraphQL.execute(GraphQL.java:47) [graphql-java-2.2.0.jar:na]
at graphql.GraphQL.execute(GraphQL.java:43) [graphql-java-2.2.0.jar:na]
...
The actual reason is that get
is invoked on an Optional
instance that has no value without a check.
In my opinion there are two possible solutions:
I'm not sure if 1) is technically possible.
I would be happy to to work on this and provide a pull request if you can give some guidance on what the desired behavior is.
Currently, Pagination follows the Spring methodology for passing variables around. However, Facebook's Relay project (https://facebook.github.io/relay/) takes a slightly different approach.
This approach is detailed in: https://facebook.github.io/relay/graphql/connections.htm. This uses a 'limit' parameter (similar to size) and a 'cursor' parameter, where we can jam in the ID or 'page' parameter.
As seen in: https://github.com/timtebeek/graphql-jpa-enum
When using the following:
public enum Genre {
DRAMA, HORROR, MYSTERY, THRILLER
}
@Entity
public class Book {
@Id
Long id;
String title;
@ManyToOne
Author author;
@Enumerated(EnumType.STRING)
Genre genre;
}
And loading my application context in a test, I get the following stacktrace:
...
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.crygier.graphql.GraphQLExecutor]: Factory method 'graphQLExecutor' threw exception; nested exception is java.lang.UnsupportedOperationException: Attribute could not be mapped to GraphQL: org.hibernate.jpa.internal.metamodel.SingularAttributeImpl@54ae1240
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:189) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:588) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
... 42 common frames omitted
Caused by: java.lang.UnsupportedOperationException: Attribute could not be mapped to GraphQL: org.hibernate.jpa.internal.metamodel.SingularAttributeImpl@54ae1240
at org.crygier.graphql.GraphQLSchemaBuilder.getAttributeType(GraphQLSchemaBuilder.java:151) ~[classes/:na]
at org.crygier.graphql.GraphQLSchemaBuilder.getObjectField(GraphQLSchemaBuilder.java:96) ~[classes/:na]
...
Trace debugging reveals that the expection is raised for Book.genre
. How can the model generation be updated to allow for such a mapped enum field?
Currently in GraphQLSchemaBuilder, lines 135 and 157 throw an UnsupportedOperationException for attribute types that aren't supported.
When the error is printed to the log, it only shows something like Caused by: java.lang.UnsupportedOperationException: Attribute could not be mapped to GraphQL: org.hibernate.ejb.metamodel.SingularAttributeImpl@3fb97fd
.
This would be more useful if instead of org.hibernate.ejb.metamodel.SingularAttributeImpl@3fb97fd
, it printed the model class and attribute that is not supported.
It would be awesome to have auto generated mutations based on JPA entities, just like queries. Is this feature on the roadmap?
With the recent changes by @lestard in #15, #17 & #18 I thought this might be a good time to up the version number and push a release to some Maven repository. That way I can update my dependency in a downstream project and release that.
What are your thoughts on this @jcrygier ?
how to perform mutation operation ...i want to insert record so it is possible and how ?
so plz.replay asap
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.