Spring Boot locking code to get an unique id - spring-boot

I wrote a controller that must return an unique String. The requirement is that two calling of this controller never return the same String, even after years and even if the code will scale to more VMs.
My question is if the following code is correct to achieve to declared purpose, or if you have any hint.
Controller:
#RestController
public class UtilityController {
#Autowired
UtilityServices utilityServices;
#GetMapping("/uniqueIdentifier")
#ResponseBody
public String uniqueIdentifier() {
return utilityServices.getUniqueIdentifier();
}
Service:
#Service
public class UtilityServices {
#Autowired
private UniqueIdRepository uniqueIdRepository;
#Transactional
public String getUniqueIdentifier() {
String uniqueId = RandomString.getSecureRandomString();
while (uniqueIdRepository.existsById(uniqueId)) {
uniqueId = RandomString.getSecureRandomString();
}
uniqueIdRepository.save(new UniqueId(uniqueId));
return uniqueId;
}
}
Entity:
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#EqualsAndHashCode
#ToString
public class UniqueId implements Serializable {
#Id
private String uniqueId;
}
Repository:
public interface UniqueIdRepository extends CrudRepository<UniqueId, String> {
}
That's all. I omit the code the RandomString class because it's not relevant in this context: I wrote a code based on SecureRandom, it is very likely that each time it returns a different String, but I have no guarantees about it. Let's assume that sooner or later my RandomString.getSecureRandomString() method can return the same String.
I'm not sure if the #Transactional annotation guarantees that the getUniqueIdentifier() method never throws an error.

The much better idea at your case will be using UUID:
Thus, anyone can create a UUID and use it to identify something with near certainty that the identifier does not duplicate one that has already been, or will be, created to identify something else. Information labelled with UUIDs by independent parties can, therefore, be later combined into a single database or transmitted on the same channel, with a negligible probability of duplication.
#Service
public class UtilityServices {
#Autowired
private UniqueIdRepository uniqueIdRepository;
#Transactional
public String getUniqueIdentifier() {
String uniqueId = String.format("%s-%s",
RandomStringUtils.randomAlphanumeric(4),
UUID.randomUUID().toString().replace("-", "")
);
// you could left this check
while (uniqueIdRepository.existsById(uniqueId)) {
uniqueId = UUID.randomUUID().toString().replace("-", "");
}
uniqueIdRepository.save(new UniqueId(uniqueId));
return uniqueId;
}
}
BTW you could use #Data for Model:
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
public class UniqueId implements Serializable {
private static final long serialVersionUID = 0L;
#Id
private String uniqueId;
}
And don't forget about serialVersionUID.
Useful references:
What is a serialVersionUID and why should I use it?
thanks to #M.Deinum for #Data details for entitites

Related

Instructing Sping Data MongoDB to use correct mapping between ObjectId and its class

I cannot retrieve the 2nd level nested objects in Spring Data MongoDB
I have nested collection in MongoDB to retrieve with Spring. Imagine this schema
#Data
#Builder
#Document(collection = "emitted")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Emitter{
#Id
private String id;
#Field("installation")
#DocumentReference(lazy = true)
private Installaton installation;
// other fields
#Data
#Builder
#Document(collection = "installation")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Installation {
#Id
private String id;
#Field("subject")
#DocumentReference(lazy = true)
private Subject subject;
// other fields
#Data
#Builder
#Document(collection = "subject")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Subject {
#Id
private String id;
// other fields
Plus, I have MapStruct to map nested object field to string, for the purpose of avoiding cyclic reference introducing the search by id of the collection:
#ObjectFactory
public <T> T map(#NonNull final String id, #TargetType Class<T> type) {
return mongoTemplate.findById(id, type);
}
Everything works at first level, but at nested level I have this error:
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.bson.types.ObjectId] to type [com.package.collections.Subject]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:322)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:195)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:175)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.doConvert(MappingMongoConverter.java:1826)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.doConvert(MappingMongoConverter.java:1818)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getPotentiallyConvertedSimpleRead(MappingMongoConverter.java:1337)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getPotentiallyConvertedSimpleRead(MappingMongoConverter.java:1311)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$DefaultConversionContext.convert(MappingMongoConverter.java:2371)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$ConversionContext.convert(MappingMongoConverter.java:2174)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1936)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readProperties(MappingMongoConverter.java:638)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.populateProperties(MappingMongoConverter.java:549)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:527)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readDocument(MappingMongoConverter.java:491)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:427)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:423)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:120)
at org.springframework.data.mongodb.core.MongoTemplate$ReadDocumentCallback.doWith(MongoTemplate.java:3326)
at org.springframework.data.mongodb.core.MongoTemplate.executeFindOneInternal(MongoTemplate.java:2940)
at org.springframework.data.mongodb.core.MongoTemplate.doFindOne(MongoTemplate.java:2618)
at org.springframework.data.mongodb.core.MongoTemplate.doFindOne(MongoTemplate.java:2588)
at org.springframework.data.mongodb.core.MongoTemplate.findById(MongoTemplate.java:922)
at com.package.myapp.services.mapper.ReferenceMapper.map(ReferenceMapper.java:26)
at com.package.myapp.services.mapper.InstallationMapperImpl.toEntity(InstallationMapperImpl.java:102)
When asking the conversion, the findById works correctly and retrieve the object and the nested one. It fails when the request is for 2nd level nested object, where the ObjectId is retrieved but cannot be converted and fails.
I'm answering myself because I found a solution suited for my problem.
I only needed the entity object with the id, so I wrote a converter:
public class ObjectIdToSubjectConverter implements Converter<ObjectId, Subject> {
#Override
public Subject convert(ObjectId source) {
return Subject.builder().id(source.toString()).build();
}
}
And register it as a bean:
#Configuration
public class MongoConfig {
#Bean
public MongoCustomConversions mongoCustomConversions() {
return new MongoCustomConversions(Collections.singletonList(new ObjectIdToSubjectConverter());
}
}

Controller for custom update query using Spring Boot

I am trying to make a rating counter on a website. When the like button is clicked, the rating value will be increased by one. I want to do this by using a custom update query. I have no idea how to write the controller class since this doesn't involve a request body. I tried to use patch mapping but obviously it's not right.
// model
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Entity
#Table(name = "review")
public class Review implements Serializable{
private static final long serialVersionUID = -1467739098650796381L;
#Id
#GeneratedValue
#Column(name="REVIEWID")
Integer reviewId;
#Column(name="SUBJECT")
String subject;
#Column(name="RECOMMENDATION")
String recommendation;
#Column(name="RATING", columnDefinition = "integer default 0")
Integer rating;
#Column(name="TIMESTAMP")
String time;
}
//Repository
#Repository
public interface ReviewRepository extends JpaRepository<Review, Integer>{
#Modifying
#Query("UPDATE review set rating = rating + 1 WHERE reviewid = ?1")
void addOneRating(Integer reviewId);
}
//Service
public class ReviewService {
#Autowired
ReviewRepository reviewRepository;
public void addRating(Integer id) {
reviewRepository.addOneRating(id);
}}
//controller
#RestController
public class ReviewController {
#PatchMapping("/addRating/{id}")
public void addRating(#PathVariable Integer id) {
reviewService.addRating(id);
}}
u are returning nothing u should redirect to the page again after updating and u can use #PostMapping also u should write #Transactional top on ReviewRepository
#RestController
public class ReviewController {
#PatchMapping("/addRating/{id}")
public void addRating(#PathVariable Integer id) {
reviewService.addRating(id);
}}

CascadeType Merge is ignored when Persist is set

Hy all
I'm having a hard time solving the following spring jpa problem.
Let's say I have the following simple data model (two entities with a one direction relationship between the two)
#Accessors(chain = true) #Getter #Setter #NoArgsConstructor #AllArgsConstructor
#MappedSuperclass
public class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Version
private Long version;
}
#Accessors(chain = true) #Getter #Setter #NoArgsConstructor #AllArgsConstructor
#Entity
public class Entity1 extends AbstractEntity {
private String name;
}
#Accessors(chain = true) #Getter #Setter #NoArgsConstructor #AllArgsConstructor
#Entity
public class Entity2 extends AbstractEntity {
private String name;
#ManyToOne(cascade={ALL})
private Entity1 entity1;
}
and the following plumbing to store them
public interface Entity1Dao extends JpaRepository< Entity1, Long >, JpaSpecificationExecutor< Entity1 > {
Entity1 findByName(String name);
}
public interface Entity2Dao extends JpaRepository< Entity2, Long >, JpaSpecificationExecutor< Entity2 > {
Entity2 findByName(String name);
}
#Service
public class StoreService {
#Autowired
Entity1Dao dao1;
#Autowired
Entity2Dao dao2;
#Transactional
public Entity1 saveEntity1(Entity1 e) {
return dao1.save(e);
}
#Transactional
public Entity2 saveEntity2(Entity2 e) {
return dao2.save(e);
}
public Entity1 loadEntity1ByName(String name) {
return dao1.findByName(name);
}
}
#SpringBootApplication
public class JpaDemoApplication {
public static void main(String[] args) {
SpringApplication.run(JpaDemoApplication.class, args);
}
}
And the following test
#SpringBootTest
#TestMethodOrder(value = MethodOrderer.OrderAnnotation.class)
class JpaDemoApplicationTests {
#Autowired
StoreService store;
#Test
#Order(1)
void contextLoads() {
assertThat(store).isNotNull();
}
#Test
#Order(2)
void insertEntity1() {
store.saveEntity1(new Entity1("test entity1"));
Entity1 saved = store.loadEntity1ByName("test entity1");
assertThat(saved).isNotNull().hasNoNullFieldsOrProperties();
}
#Test
#Order(4)
void insertEntity2WithNewEntity1() {
store.saveEntity2(new Entity2("with new entity1", new Entity1("new entity1")));
}
#Test
#Order(5)
void insertEntity2WithExistingEntity1() {
store.saveEntity2(new Entity2("with saved entity1", store.loadEntity1ByName("test entity1")));
}
}
the last test (i.e. insertEntity2WithExistingEntity1) fails with the following exception
org.hibernate.PersistentObjectException: detached entity passed to
persist: com.example.jpaDemo.Entity1
If I change the CascadeType in Entity2 to MERGE, that test passes but the insertEntity2WithNewEntity1 fails with the following exception
org.hibernate.TransientPropertyValueException: object references an
unsaved transient instance - save the transient instance before
flushing : com.example.jpaDemo.Entity2.entity1 ->
com.example.jpaDemo.Entity1
I've tried multiple combination of cascading types bute it seems that as soon as PERSIST is used, the last test fails (and ALL includes PERSIST).
I would have expected that if MERGE and PERSIST are set, they would both be active but form the test it looks like MERGE is ignored when PERSIST is set.
Any clues, tips, hints at what I'm doing wrong so that both tests run???
EDIT
The tests are suppose to mimick the behaviour of a REST service endpoint reveiving and saving json reprensentation of an Entity1.
The json for the third test would be
{ name: "with new entity1", entity1: { name: "new entity1"}}
The json for the fourth would be
{ name: "with new entity1", entity1: { id: 1, version: 0, name: "test entity1"}}
JPA should persists the entity1 in the third test because it's id is null but should merge the one in the fourth test because it's id is not null.
I am however unable to do both, it's either one or the other.
EDIT 2
I've modified Entity1 slightly to have a reference to the list of Entity2 associated to it and annotated it with #OneToMany and the same cascading type as in Entity2 and it's the same behavior.
When I set the cascading type to MERGE and only Merge, I'm able to save a new entity that has a reference with an existing one but I can't save a new entity with a reference to a new one.
When I set the cascading type to PERSIST (i.e PERSIST on its own, PERSIST and MERGE or ALL), it's the oppposit; I can save a new entity with a reference to anther new entity but I can't save a new entity with a reference to an already existing one.
So it's seem that when PERSIST is set, it overrides the behavior of MERGE. That, to me, is a bug. Is it not?
I've uploaded the source to github in case you want to experiment or take a look at it yourself. https://github.com/willix71/persistVsMerge.git
You need to add #Transactional on your last test. The entity loaded is detached as there is no outer transaction, you can't persist it.
#Test
#Order(5)
#Transactional
void insertEntity2WithExistingEntity1() {
store.saveEntity2(new Entity2("with saved entity1", store.loadEntity1ByName("test entity1")));
}
I'm not sure if this is relevant anymore, but the code below works as I would expect. Removing "cascade = CascadeType.PERSIST" will fail the persist test with "object references an unsaved transient instance".
I also noticed in your github repo that you are attempting to do cascading both from parent to child and child to parent. I think this is the root cause of your issues.
Entities:
#Entity
#Table(name = "users")
#Getter
#Setter
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
UUID id;
#ManyToOne(cascade = CascadeType.PERSIST)
Address address;
}
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#OneToMany(mappedBy = "address")
List<User> user;
}
Repositories:
public interface UserRepository extends JpaRepository<User, UUID> {
}
public interface AddressRepository extends JpaRepository<Address, UUID> {
}
Tests:
#DataJpaTest
#Import(DataSourceConfig.class)
class UserRepositoryTest {
#Autowired
private UserRepository userRepository;
#Autowired
private AddressRepository addressRepository;
#Test
void testMerge() {
var address = new Address();
addressRepository.save(address);
var user = new User();
user.setAddress(address);
userRepository.save(user);
assertThat(userRepository.findAll()).contains(user);
assertThat(addressRepository.findAll()).contains(address);
}
#Test
void testPersist() {
var address = new Address();
var user = new User();
user.setAddress(address);
userRepository.save(user);
assertThat(userRepository.findAll()).contains(user);
assertThat(addressRepository.findAll()).contains(address);
}
}

Spring Data REST convert/show EmbeddedId including ManyToOne

I have a question for my project in Spring Data REST.
My model includes two tables with EmbeddedIds.
The first table (name=B) consist of two integers.
The second table (name=A) consist of a simple FK and the model of B (includes the EmbeddedId).
Now, if I make a request for table B, I'll get the two IDs.
However, if I make a request for table A, I wont get the IDs..
So I overrid the toString()-method in my EmbeddedId-class, to return at least the IDs right in the URI-link.
I read about BackendIdConverter or Spring core.converter and tried to convert the IDs right, but I wasn't able to reach my goal (got errors). So now, I need your help!
To fully understand my problem, here's my structure (as demo):
#Embeddable
public class IDsFromA implements Serializable {
#ManyToOne
#JoinColumn(name="cID")
private C c;
#ManyToOne
#JoinColumns({
#JoinColumn(name="b_1", referencedColumnName="b_1"),
#JoinColumn(name="b_2", referencedColumnName="b_2")
})
private B b;
}
#Embeddable
public class IDsFromB implements Serializable {
private int b_1;
private int b_2;
}
#Entity
public class A {
#EmbeddedId
private IDsFromA idsFromA;
// ...
}
#Entity
public class B {
#EmbeddedId
private IDsFromA idsFromA;
// ...
}
#Entity
public class c {
#Id
private int cID;
// ...
}
The Jackson JSON serializer will by default also serialize any public get..() methods. So you can simply add a couple of methods to return the relevant data and the values should be in the response:
e.g.
#Entity
public class A {
#EmbeddedId
private IDsFromA idsFromA;
public int getValueOne(){
return idsFromA.getB().getIdsFromB().getB_1();
}
public int getValueTwo(){
return idsFromA.getB().getIdsFromB().getB_2();
}
}

Spring Data Neo4J #Indexed(unique = true) not working

I'm new to Neo4J and I have, probably an easy question.
There're NodeEntitys in my application, a property (name) is annotated with #Indexed(unique = true) to achieve the uniqueness like I do in JPA with #Column(unique = true).
My problem is, that when I persist an entity with a name that already exists in my graph, it works fine anyway.
But I expected some kind of exception here...?!
Here' s an overview over basic my code:
#NodeEntity
public abstract class BaseEntity implements Identifiable
{
#GraphId
private Long entityId;
...
}
public class Role extends BaseEntity
{
#Indexed(unique = true)
private String name;
...
}
public interface RoleRepository extends GraphRepository<Role>
{
Role findByName(String name);
}
#Service
public class RoleServiceImpl extends BaseEntityServiceImpl<Role> implements
{
private RoleRepository repository;
#Override
#Transactional
public T save(final T entity) {
return getRepository().save(entity);
}
}
And this is my test:
#Test
public void testNameUniqueIndex() {
final List<Role> roles = Lists.newLinkedList(service.findAll());
final String existingName = roles.get(0).getName();
Role newRole = new Role.Builder(existingName).build();
newRole = service.save(newRole);
}
That's the point where I expect something to go wrong!
How can I ensure the uniqueness of a property, without checking it for myself??
P.S.: I'm using neo4j 1.8.M07, spring-data-neo4j 2.1.0.BUILD-SNAPSHOT and Spring 3.1.2.RELEASE.
I walked into the same trap... as long as you create new entities, you will not see the exception - the last save()-action wins the battle.
Unfortunately, the DataIntegrityViolationException will be raised only in case of update an existing entity!
A detailed description of that behaviour can be found here:
http://static.springsource.org/spring-data/data-graph/snapshot-site/reference/html/#d5e1035
If you are using SDN 3.2.0+ use the failOnDuplicate attribute:
public class Role extends BaseEntity
{
#Indexed(unique = true, failOnDuplicate = true)
private String name;
...
}

Resources