Spring MVC: I can't modify the prodcutcode (primary key) - spring

I'm creating a simple CRUD project and having a problem modifying the primary key with the Update method.
This is done through the Json; while for the other fields I have no problems, the only problem I am having is in changing the primary key.
Here the code:
Controller:
#RequestMapping(value = "/updpro/{productcode}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public boolean updateProduct(#RequestBody Product product, #PathVariable("productcode") String productcode){
product.setProductcode(productcode);
return productDao.updateProduct(product);
}
Dao:
public boolean updateProduct(Product product) {
boolean status=false;
try {
jdbcTemplate.update("update products set productcode=?, name=?, category=? where productcode=?", product.getProductcode(), product.getName(), product.getCategory(), product.getProductcode());
status=true;
} catch (Exception e) {
e.printStackTrace();
}
return status;
}

You should not use productCode as primary key, a primary key is not supposed to change.

Your logic is wrong before setting productcode assign the old value to some variable.
public boolean updateProduct(#RequestBody Product product,
#PathVariable("productcode") String productcode){
String oldvalue = product.getProductCode();
product.setProductcode(productcode);
return productDao.updateProduct(product, oldvalue);
}
Modify value as below.
jdbcTemplate.update("update products set productcode=?,
name=?, category=? where productcode=?",
product.getProductcode(), product.getName(),
product.getCategory(), oldvalue);

Related

How do I add a Type to a graphql-java-annotations project?

The documentation for graphql-java-annotations doesn't do such a great job of telling me how to add a Custom Scalar to my schema: https://github.com/Enigmatis/graphql-java-annotations/tree/v8.0.1#annotations-schema-creator
What I need is to create some 'scalar Date' in the Schema. It is unclear how to do this with the AnnotationsSchemaCreator builder thing.
GraphQLSchema schema = AnnotationsSchemaCreator.newAnnotationsSchema()
.query(Query.class) // to create you query object
.mutation(Mutation.class) // to create your mutation object
.subscription(Subscription.class) // to create your subscription object
.directive(UpperDirective.class) // to create a directive
.additionalType(AdditionalType.class) // to create some additional type and add it to the schema
.typeFunction(CustomType.class) // to add a typefunction
.setAlwaysPrettify(true) // to set the global prettifier of field names (removes get/set/is prefixes from names)
.setRelay(customRelay) // to add a custom relay object
.build();
The docs give me just that. Is a typeFunction what I need here? Do I have to first get the graphql-java "Custom Scalar" stuff set up and put that into the typeFunction?
What's happening right now is that my graphql-java-annotations Types which need the Date type...
public abstract class BasePart {
#GraphQLField
#GraphQLNonNull
#JsonIgnore
public String id;
...
#GraphQLField
public Date createdOn;
...
}
Get into the Schema without the Date scalar defined so the GraphiQL UI is rejecting it with errors like...
Error: Date fields must be an object with field names as keys or a function which returns such an object.
at invariant (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:13:12678)
at defineFieldMap (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:14:16395)
at e.getFields (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:14:22028)
at http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:22055
at typeMapReducer (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:22227)
at http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:22200
at Array.forEach (<anonymous>)
at http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:22082
at typeMapReducer (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:22227)
at typeMapReducer (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:21564)
I'm trying to figure out how to get that information into the, what, AnnotationsSchemaCreator.newAnnotationsSchema() builder?
How do you add a Custom Scalar to a graphql-java-annotations project?
The TypeFunction is the key. You pass the TypeFunction when you are building the Schema with the AnnotationsSchemaCreator. The following code effectively got scalar Date into the service's GraphQL Schema
graphQLSchema = AnnotationsSchemaCreator.newAnnotationsSchema()
.query(QuerySchema.class)
.setAlwaysPrettify(true)
.typeFunction(new MyDateTypeFunction()) // <-- This got scalar Date onto the schema
.build();
The TypeFunction itself realizes the support for the scalar Date.
public class MyDateTypeFunction implements TypeFunction {
#Override
public boolean canBuildType(Class<?> clazz, AnnotatedType annotatedType) {
return clazz == java.util.Date.class;
}
#Override
public GraphQLType buildType(
boolean b,
Class<?> clazz,
AnnotatedType annotatedType,
ProcessingElementsContainer processingElementsContainer) {
return MY_DATE;
}
public static final GraphQLScalarType MY_DATE = GraphQLScalarType
.newScalar()
.name("Date")
.description("Coerce java.util.Date to/from a String representation of the long value of getTime().")
.coercing(
new Coercing() {
#Override
public Object serialize(Object dataFetcherResult) throws CoercingSerializeException {
if (dataFetcherResult instanceof Date) {
final String result = String.format("%d", ((Date) dataFetcherResult).getTime());
return result;
}
final String message =
String.format("Expected type java.util.Date but found %s", typeName(dataFetcherResult));
throw new CoercingSerializeException(message);
}
#Override
public Object parseValue(Object input) throws CoercingParseValueException {
if (input instanceof String) {
try {
return stringToDate((String) input);
} catch (NumberFormatException nfe) {
final String message = String.format("NumberFormatException %s", nfe.getMessage());
throw new CoercingParseValueException(message);
}
}
final String message = String.format("Unable to parseValue %s to a java.util.Date", input);
throw new CoercingParseValueException(message);
}
#Override
public Object parseLiteral(Object input) throws CoercingParseLiteralException {
if (input instanceof StringValue) {
try {
final String inputStringValue = ((StringValue) input).getValue();
return stringToDate(inputStringValue);
} catch (NumberFormatException nfe) {
final String message = String.format("NumberFormatException %s", nfe.getMessage());
throw new CoercingParseLiteralException(message);
}
}
final String message = String.format("Unable to parseLiteral %s to a java.util.Date", input);
throw new CoercingParseLiteralException(message);
}
}
)
.build();
public static Date stringToDate(String input) throws NumberFormatException {
final long inputAsLong = Long.parseLong(input);
return new Date(inputAsLong);
}
public static String typeName(Object input) {
return input == null ? "null" : input.getClass().getName();
}
}
Note that I'm not recommending that you represent java.util.Date as the String value of the long getTime(), java.time.Instant's ISO-8601 is so much more readable, but my service needed this string value and this is how I got it into a graphql-java-annotation's project schema.

Spring #Cacheable for method no parameter

I want to cache some db data. for example Cache Customer and use customer.id as the key.
How could I set the key if I want to load all customers (allCustomer() in the code) ?
#Cacheable(value = "customer", key = "#customerID")
public Customer getCustomer(Long customerID) {
return getCustomerData(customerID);
}
// How to setup this key?
#Cacheable(value = "customer", key = "?")
public List<Customer> allCustomer(){
return db.values().stream().collect(Collectors.toList());
}
#CachePut(value = "customer", key = "#customer.id")
public void updateCustomer(Customer customer){
db.put(customer.getId(), customer);
}
#CacheEvict(value = "customer", key = "#customerID")
public void deleteCustomer(Long customerID){
db.remove(customerID);
}
I would recommend using #CachePut instead of #Cacheable. In the case that a new entry is added to the DB from outside of this application instance, the cache would not contain that new value.
You can use #result.id to tell Spring which value to use as a key and I've included a conditional so that you don't get strange errors in case of a null value.
#CachePut(value = "customer", key = "#result.id", condition = "#result != null")
It's impossible to do it for collections with the Spring's annotations - with #Cacheable you'd have just one element in a cache with a computed key and a value with the whole list inside.
If performance is not that important in your app, use getCustomer(...) in a loop.
Otherwise, you'll need to update your cache manually. Unfortunately, Cache interface doesn't provide a method to retrieve all keys/values/key-value pairs from a cache, so a bit of casting is required.
The example for the default in-memory cache (spring.cache.type=simple):
#Autowired
private org.springframework.CacheManager cacheManager;
public List<Customer> allCustomers() {
ConcurrentMap<Long, Customer> customerCache = (ConcurrentMap<Long, Customer>)
cacheManager.getCache("customer").getNativeCache();
if (!customerCache.isEmpty()) {
return new ArrayList<>(customerCache.values());
}
List<Customer> customers = db.values().stream().collect(Collectors.toList());
customers.forEach(customer -> customerCache.put(customer.getId(), customer));
return customers;
}
Or for spring.cache.type=jcache with backed EhCache 3:
#Autowired
private org.springframework.CacheManager cacheManager;
public List<Customer> allCustomers() {
javax.cache.Cache<Long, Customer> customerCache = (javax.cache.Cache<Long, Customer>)
cacheManager.getCache("customer").getNativeCache();
Iterator<Cache.Entry<Long, Customer>> iterator = customerCache.iterator();
List<Customer> cachedCustomers = new ArrayList<>();
while (iterator.hasNext()) {
Cache.Entry<Long, Customer> entry = iterator.next();
cachedCustomers.add(entry.getValue());
}
if (!cachedCustomers.isEmpty()) {
return cachedCustomers;
}
List<Customer> customers = db.values().stream().collect(Collectors.toList());
customers.forEach(customer -> customerCache.put(customer.getId(), customer));
return customers;
}
The same can be done similarly for any other cache type (redis, hazelcast, caffeine etc.).
The corresponding eviction method can be written much easier:
#CacheEvict(value = "customer", allEntries = true)
public void deleteAllCustomers(){
db.removeAll(); //pseudocode
}

spring boot - mongodb - DBRef Hashset accepts duplicates

I have a user entity with a set of favorite items. and i annotate the field as follows
class user {
#Id
private String id;
#DBREF
private HashSet <Item> favorites;
//empty and full constructor + getters and setters
}
when i want to add a new item to the set i get the user from the database, get his favorites insert new item and save user. for adding and removing items I use the add and remove functions of the Set interface.
the problem: the set accepts duplicates, and remove function does not remove from the set
EDIT: here is how I add an item to the set:
public void addItem(Strin username, String id){
Item item= itemService.findById(id);
User user = userService.findByUsername(username);
HashSet<Item> set= user.getFavorites();
set.add(Item);
user.setFavorites(set);
this.userRepository.save(user);
}
For future reference, I had to correctly implement the equals function of class Item:
#Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Item)) {
return false;
}
Item item = (Item) o;
// wrong (for comparing Strings): return id == item.getId()
// right:
return id.equals(item.getId());
}
#Override
public int hashCode() {
return Objects.hash(id);
}

Caching with Spring and ehcache doesnt work as expected

I have a Product model object like this -
class ProductDTO {
int id;
String code;
String description;
//getters and setters go here
}
I am writing a service (code below) that looks up products by id or code and returns their description. I am using Spring 4 and ehcache to cache the results.
I have 2 methods - one for lookup by id and one for lookup by code - they are getProductByCode and getProductById. Both return the description as a string. They do so by calling getAllProducts() which returns a list of all products. The callers then search the list for a product matching the id or code and return the description.
getAllProducts() also calls 2 methods with #CachePut for each product - to save the description Strings in cache - by key code and id.
Caching works properly if the same arguments are passed for code or id to to the getProductByCode and getProductById methods. But if I pass a different argument, getAllProducts() is called again.
How do I achieve the desired behavior - where every time a call is made to getAllProducts(), all descriptions get cached and a subsequent call looks up the cache rather than going to the repository?
public class ProductServiceImpl implements ProductService {
#Autowired
ProductsRepository ProductRepo;
#Override
public List<ProductDTO> getAllProducts() {
List<ProductDTO> products = ProductRepo.getAllProducts();
for(ProductDTO prodDTO : products) {
String desc = prodDTO.getDescription();
String code = prodDTO.getCode();
int id = prodDTO.getId();
putDescriptionInCache(desc, code);
putDescriptionInCache(desc, id);
}
return products;
}
#CachePut(value = "products", key = "#id")
public String putDescriptionInCache(String description, int id){
return description;
}
#CachePut(value = "products", key = "#code")
public String putDescriptionInCache(String description, String code){
return description;
}
#Override
#Cacheable(value="products", key="#id")
public String getProductById(Integer id) throws NullPointerException {
String dtoDesc = null;
List<ProductDTO> products = getAllProducts();
for(ProductDTO currDTO : products) {
int currId = currDTO.getId();
if(id.equals(new Integer(currId))) {
dtoDesc = currDTO.getDescription();
}
}
return dtoDesc;
}
#Override
#Cacheable(value="products", key="#code")
public String getProductByCode(String code) throws NullPointerException {
String dtoDesc = null;
List<ProductDTO> products = getAllProducts();
for(ProductDTO currDTO : products) {
String currCode = currDTO.getCode();
if(currCode.equals(code)) {
dtoDesc = currDTO.getDescription();
}
}
return dtoDesc;
}
}
As it was commented by M. Deinum, the problem comes from the annotations, like CachePut or Cacheable, being transformed into an aspect at runtime. And the main limitation with that approach is that calls from the same class are not properly captured.
As you replied yourself in the comments section, moving the annotated methods to another type that is injected in the current one solves the problem.

How do I set a custom validation error on a specific form field in Play! 2.*

Basically I have a following form class.
public static class BasicForm {
#Required
public String name;
#Required
#Email
public String email;
#Required
public String password;
#Required
public String confirmPassword;
public List<ValidationError> validate() {
if (User.findByEmail(email) != null) {
List<ValidationError> validationErrorList = new ArrayList<ValidationError>();
validationErrorList.add(new ValidationError("email", "error.alreadyexists", new ArrayList<Object>()));
return validationErrorList;
}
return null;
}
}
As you can see, I am trying to validate the uniqueness of the email address.
If email is not unique, I would like to display the error message on the email field, NOT as a global error message
What is the correct way of implementing validate() method to achieve this?
You should use a validate method with the following signature:
public Map<String, List<ValidationError>> validate()
This way you can add errors to single fields like this:
Map<String, List<ValidationError>> errors = null;
if (emailIsBad) {
errors = new HashMap<String, List<ValidationError>>();
List<ValidationError> list = new ArrayList<ValidationError>();
list.add(new ValidationError("email", "email is bad"));
errors.put("email", list);
}
return errors;
please note that if you should return an empty map, it still will render the form as erroneous. If you want the validate() method to succeed, you need to return null.
Not enough rep to comment (yet), but don't forget to include the proper import statement:
import.play.data.validation.*;
I wasted a few minutes due to an incorrect statement.
This is what I came up with based on Daniel's
code:
My code checks for password confirmation, tho.
public Map<String, List<ValidationError>> validate() {
Map<String, List<ValidationError>> errors = null;
if (!password.equals(confirmPassword)) {
errors = new HashMap<>();
List<ValidationError> list = new ArrayList<>();
list.add(new ValidationError("password", "Passwords do not match"));
errors.put("password",list);
errors.put("confirmPassword", list);
}
return errors;
}

Resources