I want to test my BookFormController where I have addBook forms. I want to Mock every service which I use and everything seems to works fine but the problem is that my POST method is based on spring-data converters to make Author object and Type objects from their ids.
Now I think I would like to mock in some way those converters so when I pass author="1" it should convert book author to some object so my NotNull and NotEmpty validation on author and types passed.
Book.class:
public class Book extends Item {
#ManyToOne
#JoinColumn(name = "author_id")
#NotNull
private Author author;
Items.class
#NotEmpty
#Column(nullable = false)
private String isbn;
#NotEmpty
#Column(nullable = false)
private String title;
#Column(name = "page_number", nullable = false)
#Min(1)
#NotNull
private Integer pageNumber;
#DateTimeFormat(iso = ISO.DATE)
#Column(name = "release_date", nullable = false)
#NotNull
private LocalDate releaseDate;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "items_types", joinColumns = {
#JoinColumn(name = "item_id", referencedColumnName = "id") }, inverseJoinColumns = {
#JoinColumn(name = "type_id", referencedColumnName = "id") })
#NotEmpty
private Set<Type> types = new HashSet<>();
#OneToMany(mappedBy = "item", cascade = { CascadeType.REMOVE })
private List<Borrow> borrows;
BookFormController POST method:
#RequestMapping(value = "/add", method = RequestMethod.POST)
public String addBookAction(#Valid #ModelAttribute("book") Book book, BindingResult result, Model model,
RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
return "book/bookForm";
}
bookService.save(book);
redirectAttributes.addFlashAttribute("messageCode", "BookAdd.message.successfull");
return "redirect:/books";
}
And my tests:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { WebConfig.class })
#WebAppConfiguration
public class BookFormControllerTest {
private MockMvc mockMvc;
#Mock
private BookService bookService;
#Mock
private TypeService typeService;
#Mock
private AuthorService authorService;
#InjectMocks
private BookFormController bookFormController;
#Before
public void init() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(bookFormController).build();
}
#Test
public void testAddBookPost() throws Exception {
Mockito.doNothing().when(bookService).save(any(Book.class));
mockMvc.perform(post("/books/add")
.param("title", "Spring MVC")
.param("isbn", "123QWE")
.param("author", "1")
.param("releaseDate", "2010-10-10")
.param("pageNumber", "256")
.param("types", "1")
.param("types", "2"))
.andExpect(status().is3xxRedirection())
.andExpect(flash().attribute("messageCode", "BookAdd.message.successfull"))
.andExpect(redirectedUrl("/books"));
}
And this test doesn't work as it should because of converters. When I remove NotNull and NotEmpty from book/items objects and don't send those values in POST, this test works. So what should I do?
Related
I have a entity call Circuit.
#Entity
public class Circuit implements Comparable<Circuit>, Serializable {
#Column
private String id;
#OneToMany(mappedBy = "circuit", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<Step> workflow = new HashSet<>();
...
}
I have a class called CircuitLight
public class CircuitLight {
private String id;
private Set<Step> workflow;
/* constructor, getters and setters */
}
In my CircuitRepository, i'm trying to make a projection
#Transactional(readOnly = true)
#Query("select new com.docapost.circuit.CircuitLight(c.id, c.workflow) from Circuit c where c.account.siren = :siren")
Set<CircuitLight> findAllByAccountSirenProjection(#Param("siren") String siren);
When i execute, i have a error message:
could not extract ResultSet; SQL [n/a] com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'circuit0_.id' in 'on clause'
I try with other entity. Every time i have a property with a relation #OneToMany, i have the issue...
Is it possible to make a projection with class (Without use a interface) when there are a relation OneToMany ?
UPDATE:
Step.class
#Entity
public class Step implements Comparable<Step>, Serializable {
private static final List<String> INDEXABLE_PROCESSES = Arrays.asList(
ParapheurWorkflowModel.SERVER,
ParapheurWorkflowModel.SIGN,
ParapheurWorkflowModel.VISA
);
#Id
#GeneratedValue
#Expose
#SerializedName("step_id")
public long id;
#ManyToOne
public Circuit circuit;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(joinColumns = #JoinColumn(name = "step_id"), inverseJoinColumns = #JoinColumn(name = "technicalGroup_id"))
private List<TechnicalGroup> technicalGroups = new ArrayList<>();
#Column(name = "step_type", nullable = false)
#Expose
#SerializedName("subprocess_ref")
public String type;
#Column(nullable = false)
public int orderIndex;
/* contructor, getters and setters */
}
UPDATE 2:
Hum.... My bad, in my circuit class, i have a EmbeddedId
#EmbeddedId
private CircuitPK key;
#Embeddable
public static class CircuitPK implements Serializable {
public String id;
public String siren;
}
I try with this code in Step.class
#ManyToOne
#JoinColumns(value = {
#JoinColumn(name = "circuit_siren", referencedColumnName = "siren"),
#JoinColumn(name = "circuit_id", referencedColumnName = "id")
})
public Circuit circuit;
The result is the same
Write the following code in the Step entity
#ManyToOne
#JoinColumn(name="id", nullable=false)
private Circuit circuit;
I have this below entity classes.
Parent Entity:
---------------
public class MLW implements Serializable {
#Id
#Column(name = "mlwId", updatable = false, nullable = false)
private String mlwId;
#OneToOne(optional = false, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="mlwId")
private MLWJob mlwJob;
private Date createdDate;
private Date lastUpdatedDate;
}
public class MLWJob implements Serializable {
#Id
private String mlwId;
#Column(name = "Id")
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="mlwId")
private List<MLWJobItem> items;
}
public class MLWJobItem implements Serializable {
#Id
private String itemId;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="itemId")
private List<MLWJobItemExtension> mlwJobItemExtensions;
}
public class MLWJobItemExtension implements Serializable {
#Id
private String extnId;
#Id
private String itemId;
private String name;
private String value;
}
Repository class:
------------------
public interface MLWRepository extends JpaRepository<MLW, String> {
}
When I try to call save method using above repository I am getting below exception. Could someone help me to understand and fix this issue? TIA
could not delete collection rows:
[com.cpc.wgln.entity.MLWJobItem.mlwJobItemExtensions#0105021001713441];
nested exception is org.hibernate.exception.GenericJDBCException:
could not delete collection rows:
[com.cpc.wgln.entity.MLWJobItem.mlwJobItemExtensions#0105021001713441]
User entity
#Entity
#Table(name = "T_USER")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String email;
//A user can be employee in many organizations so he look at the same time as many emplyee
#JsonManagedReference(value = "user-association")
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private Set<Association> associations;
....
Association entity
#Entity
#Table(name = "T_ASSOCIATION")
public class Association implements Serializable {
#EmbeddedId
private AssociationId associationId;
private String permission;
#ManyToOne(fetch = FetchType.LAZY)
#JsonBackReference(value = "user-association")
#JoinColumn(name = "user_id", referencedColumnName = "id", insertable = false, updatable = false)
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JsonBackReference(value = "organization-association")
#JoinColumn(name = "organization_id", referencedColumnName = "id", insertable = false, updatable = false)
private Organization organization;
POST endpoint
#PostMapping(path = "/{id}/users", consumes = MediaType.APPLICATION_JSON_VALUE)
public List<User> associateUserToOrganization(#RequestBody AssociationDTO userToOrgDTO, #PathVariable String id) {
Association association = new Association(new AssociationId(userToOrgDTO.getUserId(), userToOrgDTO.getOrganizationId()));
association.setPermission("GUEST_SET");
User userToAffect = userRepository.findById(userToOrgDTO.getUserId()).get();
Organization orgToAffect = organizationRepository.findById(userToOrgDTO.getOrganizationId()).get();
userToAffect.addAssociation(association);
orgToAffect.addAssociation(association);
organizationRepository.save(orgToAffect);
return userRepository.findAll().stream().filter(user -> !user.getAssociations().isEmpty()).collect(Collectors.toList());
}
When I add a user to the organization using POSTMAN with correct input, the famous error of serialization of lazy loading appears even I am not using fetch type Lazy
this is the error
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.ArrayList[0]->jpa.workspace.manytomanyadditional.domain.User$HibernateProxy$pL9wPAuw["hibernateLazyInitializer"])
So i made the solution as it is mentioned in this exception and i put in application.properties,
spring.jackson.serialization.fail-on-empty-beans=false
and #JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) to remove it from the json
The issue is fixed sure, but why I am supposed to consider the User->associations relationship as Lazy fetching and not eager as I chose it...Is it related to #JsonManagedReference maybe?
am using spring 2.4.1 and hibernate (core 5.4.25 & annotations 5.1.2 Final)
Remove #JsonManagedReferenced, #JsonBackReference and add these following in your config class
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jackson2HttpMessageConverter());
}
#Bean
public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(jacksonBuilder().build());
return converter;
}
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
Hibernate5Module hibernateModule = new Hibernate5Module();
hibernateModule.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING, false);
builder.modules(hibernateModule);
builder.featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
builder.featuresToDisable(MapperFeature.DEFAULT_VIEW_INCLUSION);
return builder;
}
I have a Post entity
#Entity
public class Post {
#Id
private UUID id;
#NotNull
private String title;
#NotNull
private String content;
#NotNull
private String identifier;
#NotNull
private String category;
#NotNull
#Column(name = "created_at")
private Date createdAt;
#NotNull
#Column(name = "updated_at")
private Date updatedAt;
public Post (){
}
public Post (String title, String content, String category){
this.title = title;
this.content = content;
this.category = category;
}
// rest of the getters and setters
}
And this is my Comment entity:
#Entity
public class Comment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private UUID id;
#NotNull
private String name;
#NotNull
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer identifier;
#NotNull
private String email;
#NotNull
private String content;
#NotNull
#ManyToOne
#JoinColumn(name = "post_id")
private Post postId;
#NotNull
#Column(name = "created_at")
private Date createdAt;
public Comment() {
}
public Comment(String name, String email, String content){
this.name = name;
this.email = email;
this.content = content;
}
}
And this is my post controller:
#RestController
#RequestMapping("/posts")
public class PostController {
private String getIdentifier(String str){
return String.join("-", str.split(" "));
}
#Autowired
private PostService postService;
#RequestMapping(value = "", method = {GET, HEAD})
public List<Post> getAllPosts(){
return postService.getAllPosts();
}
#RequestMapping(value = "", method = {POST, OPTIONS})
public Post addNewPost(#RequestBody Post post){
post.setId(UUID.randomUUID());
post.setIdentifier(this.getIdentifier(post.getTitle()));
post.setCreatedAt(new Date());
post.setUpdatedAt(new Date());
return postService.savePost(post);
}
#RequestMapping(value = "/{id}", method = {GET, HEAD})
public Post getOnePost(#PathVariable UUID id){
return postService.getOne(id);
}
#RequestMapping(value = "/{id}", method = DELETE)
public void deleteOnePost(#PathVariable UUID id){
postService.deleteOnePost(id);
}
}
My question is how do I fetch all the comments for each individual post, whenever I fetch all the posts?
Sorry, I come from a NoSQL background, so this is a bit daunting at first.
What you need to do is to create a bidirectional #OneToMany association from the Post to Comments:
add a field in Post class
#OneToMany(
mappedBy = "postId",
cascade = CascadeType.ALL
)
private List<Comments> comments = new ArrayList<>();
From now on, when you get Post from the database, Comments will be fetched at the same time.
Database Tables
post
tag
ref_post_tag
post and tag has a Many-to-Many relationship
Entities
Post
#Entity
#Table(name = "post")
public class Post implements Serializable{
private static final long serialVersionUID = 1783734013146305964L;
public enum Status {
DRAFT, REMOVED, LIVE;
}
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private String id;
#Column(name = "title")
private String title;
#Column(name = "create_time")
private LocalDateTime createTime;
#Column(name = "update_time")
private LocalDateTime updateTime;
#Column(name = "content")
private String content;
#Column(name = "status")
#Enumerated(EnumType.STRING)
private Status status;
#ManyToMany
#JoinTable(
name = "ref_post_tag",
joinColumns = #JoinColumn(name="post_id",referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name="tag_id", referencedColumnName = "id"))
private List<Tag> tagList;
...
}
Tag
#Entity
#Table(name="tag")
public class Tag implements Serializable{
private static final long serialVersionUID = -7015657012681544984L;
#Id
#Column(name="id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name="name")
private String name;
#Column(name="description")
private String description;
#ManyToMany(mappedBy = "tagList")
private List<Post> postList;
public Integer getId() {
return id;
}
...
}
Tag Repo
public interface TagRepo extends CrudRepository<Tag, Integer>{
}
service implementation
#Service
public class TagServiceImpl implements TagService{
#Autowired
private TagRepo tagRepo;
#Override
public void addTag(Tag tag) {
tagRepo.save(tag);
}
#Override
public Tag getTag(Integer id) {
Tag tag = tagRepo.findOne(id);
return tag;
}
#Override
public List<Tag> findAllTags() {
return CollectionUtil.toArrayList(tagRepo.findAll());
}
}
sample test (Updated)
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestContextConfiguration.class)
#Transactional
public abstract class ServiceTest {
}
public class TagServiceTest extends ServiceTest{
#Autowired
private TagService tagService;
#Autowired
private TagRepo tagRepo;
#Test
#Transactional
public void addTag() throws Exception {
Tag tag = new Tag();
tag.setName("new tag");
tag.setDescription("this is a new tag");
tagService.addTag(tag);
Tag tagCreated = tagRepo.findOne(tag.getId());
assertNotNull(tagCreated);
assertEquals(tagCreated.getName(), tag.getName());
}
#Test
public void getTag() throws Exception {
Tag tag = tagService.getTag(1); // tag "java" has an ID of "1"
assertNotNull(tag);
assertEquals(tag.getName(), "java");
assertEquals(143,tag.getPostList().size()); // 143 posts under tag "java"
}
}
Question
The sample test case passes. It means that the postList in fetched Tag is also eagerly fetched and filled.
Is Spring data repository's methods eagerly fetching by default?
If yes, what is the best way to change this to lazy fetching?