Mock loading of navigation properties - java

This is a very simplified version of my code, that illustrates the specific problem.
Is there any way that I can control what happens when accountProductRepository.refresh() is called from the test?
Somehow I need to set the ProductPojo on the AccountProductPojo created in the buyProduct() method, so I don't get a null pointer when accessing the getProduct().getName() property.
refresh uses javax.persistence.EntityManager.refresh() to load the navigation properties based on the id's set in the buyProduct() method.
public class ProductServiceTest {
#InjectMocks
IProductService productService = new ProductService();
#Mock
IWriteANoteService writeANoteService;
#Mock
IAccountProductRepository accountProductRepository;
#Test
public void buyProductTest() {
productService.buyProduct(1l, 1l);
}
}
#Service
public class ProductService implements IProductService {
#Autowired
IWriteANoteService writeANoteService;
#Autowired
IAccountProductRepository accountProductRepository:
public void buyProduct(Long productId, Long accountId) {
AccountProductPojo accountProduct = new AccountProductPojo();
accountProduct.setProductId(productId);
accountProduct.setAccountId(accountId);
accountProductRepository.persist(accountProduct);
// load navigation properties
accountProductRepository.refresh(accountProduct);
writeANoteService.writeAccountNote(accountId, "Bought product " + accountProduct.getProduct().getName());
}
}
#Entity
#Table(name = "account_product")
public class AccountProductPojo {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "account_id")
private Long accountId;
#Column(name = "product_id")
private Integer productId;
#ManyToOne
#JoinColumn(name = "product_id", insertable = false, updatable = false)
private ProductPojo product;
#OneToOne(fetch = FetchType.LAZY, targetEntity = AccountPojo.class)
#JoinColumn(name = "account_id", insertable = false, updatable = false)
private AccountPojo account;
// getters and setters
}

This seems to be a fairly classic case of mocking a void method.
You could try something like this:
Mockito.doAnswer(new Answer() {
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
AccountProductPojo accountProduct = (AccountProductPojo) args[0];
accountProduct.setProduct(new ProductPojo(PRODUCT_ID_CONSTANT, PRODUCT_NAME_CONSTANT));
return null;
}}).when(accountProductRepository).refresh(Mockito.any());
The key here is that when refresh() is called on the mock you call setProduct() on the POJO which was passed as an argument to the refresh() call in order to avoid the later null pointer exception.

Related

SpringBoot RestAPI Controller methods are not working

I wrote a few service methods. When I implement CommandLineRunner to my SpringApplication and try to execute service methods in run methods I can see they're working.
I mean like this approach, I can reach the data via service methods. So I can say yes my services are working right.
public void run(String... args) throws Exception {
postService.getAllPost()
.stream()
.forEach(entry -> System.out.println(entry.getTitle()));
}
But When I try to use my controllers via postman. For the User class, everything is fine.
public List<UserDto> getAllUsers() {
return userRepository.findAll()
.stream()
.map(userMapper::toDto)
.collect(toList());
}
#GetMapping("/get")
public ResponseEntity<List<UserDto>> getAll() {
return ResponseEntity.ok(userService.getAllUsers());
}
But Comment and Post are not working.
The methods are almost same. I was wondering is it about my entities relation? Because in User entity I just have 3 different field which is Long, String , String . But the other entities are related with each others. With OneToMany relational. And I dont trust about my business plan. I mean post class includes user and comment object. Comment class includes user object etc.
This is controller method.
#GetMapping("/getAll")
public ResponseEntity<List<CommentDto>> fetchAll() {
return ResponseEntity.ok(commentService.getAllComments());
}
This is service method.
public List<CommentDto> getAllComments() {
return commentRepository.findAll().stream()
.map(commentMapper::toDto)
.collect(Collectors.toList());
}
I have three different entity class.
Which is
#Entity
#Table(name = "user_table")
public class User{
#Id
private Long id;
private String userName;
private String password;
}
#Entity
#Table(name = "post_table")
public class Post {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id" , nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private User user;
private String title;
#Lob
#Column(columnDefinition = "text")
private String text;
}
#Entity
#Table(name = "comment_table")
public class Comment {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "post_id" , nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private Post post;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id" , nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private User user;
#Lob
#Column(columnDefinition = "text")
private String text;

Base Entity custom column

i have base entity like
#MappedSuperclass
public class BaseEntityCore implements Serializable {
#CreatedBy
#Column(name = "olusturan", /* nullable = false, */ length = 50, updatable = false)
private String createdBy;
#CreatedDate
//#NotNull
#Column(name = "olusturma_tarihi", nullable = false, updatable = false)
private LocalDateTime createdDate ;
#LastModifiedBy
#Column(name = "guncelleyen", length = 50)
private String lastModifiedBy;
#LastModifiedDate
#Column(name = "guncelleme_tarihi")
private LocalDateTime lastModifiedDate;
#Column(name = "aktif")
private int aktif;
// getter and setter
and a entity extends this base entity like
#Entity
#Table(name = "foo")
#EntityListeners(value = { AbstractEntityListenerCore.class })
public class foo extends BaseEntityCore {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name="foo_name")
private String fooName;
//getter and setter
}
with spring , spring jpa. i also have entity repo like
public interface FooRepository extends JpaRepository<Foo, Long> {
Optional<Foo> findByFooName(String name);
}
now i can save entity with foo.setAktif(1). after saving foo i see on table aktif is 1. After that i run findByFooName method. this turns the object but this object has 2 aktif properties. first is aktif and value is 1 and the other is BaseEntityCore.aktif and value is 0. i check with if clause like
if(foo.getAktif()==1){
//do something
}
else {
//throws exception;
}
i cant get it why always throws exception.
You don't need your if else clause.
Just search always for Entities with "Aktif" == 1.
So extend your repo class with an other method
Optional<Foo> findByFooNameAndAktif(String name, int aktif);
and only search for the "aktif" you want.
But your question is about the 2 properties of "Aktif" right?

How to save two related objects to data base with relation OneToOne

When I'm trying to save an U object I got next exception:
org.springframework.orm.jpa.JpaSystemException: attempted to assign id from null one-to-one property [com.roc.domain.A.user]; nested exception is org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property [com.roc.domain.A.user]
I have two tables:
1. user that columns are id(auto incr, primary), name.
2. contact that columns are id, user_id(that is foreign key -> user.id) and address.
#Entity
#Table(name = "a")
public class A {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name="address")
private String address;
#OneToOne
#MapsId
private U user;
public A() {
}
// getters and setters
}
#Entity
#Table(name = "u")
public class U {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name="username")
private String userName;
#JoinColumn(name = "user_id", referencedColumnName = "id")
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
private A a;
public U(){};
}
#RunWith(SpringRunner.class)
#SpringBootTest
public class ApplicationTest {
#Autowired
private URepository uRepository;
#Test
public void simpleCrudTest() {
U user = new U("name", new A("address"));
uRepository.save(user);
}
}
You have set the cascade correctly however because the relationship is bi-directional you need to set both sides in the in-memory model.
#Test
public void simpleCrudTest() {
U user = new U("name", new A("address"));
//will work when this is added
a.setUser(user);
uRepository.save(user);
}
Otherwise, as the error states, A has a null reference for user on save.
Edit: To save using a single repository save call.
#Entity
#Table(name = "a")
public class A {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "address")
private String address;
#OneToOne
#MapsId
private U user;
public A() {
}
}
#Entity
#Table(name = "u")
public class U {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "username")
private String userName;
#JoinColumn(name = "user_id", referencedColumnName = "id")
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
private A a;
public U() {
};
// method to manage the bidirectional association
public U addToA(A a) {
this.a.add(a);
a.setUser(this);
}
}
#RunWith(SpringRunner.class)
#SpringBootTest
public class ApplicationTest {
#Autowired
private URepository uRepository;
#Test
public void simpleCrudTest() {
U user = new U();
user.addToA(new A("address"));
user.setUserName("username");
uRepository.save(user);
}
}
Also, you refer to this link.
inserting values into multiple tables using hibernate
You have to save A first, Then set saved A to U and save U.
#RunWith(SpringRunner.class)
#SpringBootTest
public class ApplicationTest {
#Autowired
private URepository uRepository;
#Autowired
private ARepository aRepository;
#Test
#Trascational
public void simpleCrudTest() {
A a = new A();
a.setAddress("address");
a = aRepository.save(a);
U user = new U("name", a);
uRepository.save(user);
}
}

How to test json api when collection is Set

I read that it is preferable to use the Set instead List in Hibernate relation.
I created two entities in relation to one to many:
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, unique = true)
private String name;
#ManyToOne
#JoinColumn(name = "company_id", nullable = false)
private Company company;
}
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, unique = true)
private String name;
#LazyCollection(LazyCollectionOption.TRUE)
#OneToMany(mappedBy = "company", cascade = CascadeType.ALL)
private Set<Product> products;
}
In relation #OneToMany set collection private Set products;
Then I try to test the return result:
#RunWith(SpringRunner.class)
#SpringBootTest
#Transactional
public class CompanyControllerTest {
private static final String API_COMPANY = "/api/company/";
#Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.build();
}
#Test
public void getById() throws Exception {
int id = 1;
this.mockMvc.perform(get(API_COMPANY + id))
.andExpect(status().isOk())
.andExpect(content().contentType(APPLICATION_JSON_UTF8))
.andExpect(jsonPath("id", is(1)))
.andExpect(jsonPath("$.name", is("Google")))
.andExpect(jsonPath("$.products", hasSize(2)))
.andExpect(jsonPath("$.products[0].id", is(1)))
.andExpect(jsonPath("$.products[0].name", is("search engine")))
.andExpect(jsonPath("$.products[0].company").doesNotExist())
.andExpect(jsonPath("$.products[1].id", is(2)))
.andExpect(jsonPath("$.products[1].name", is("adv.")))
.andExpect(jsonPath("$.products[1].company").doesNotExist());
}
}
But the problem is that the list of products is constantly changing, because I use a Set.
And it turns out that the test either passes or fails, since the order of the products changes.
My question is how to test the result when using Set.
You can get all the elements with [*] and give a Matchers.containsInAnyOrder(T...) all the elements that you want to check.
Something like this:
this.mockMvc.perform(get(API_COMPANY + id))
.andExpect(status().isOk())
.andExpect(content().contentType(APPLICATION_JSON_UTF8))
.andExpect(jsonPath("id", is(1)))
.andExpect(jsonPath("$.name", is("Google")))
.andExpect(jsonPath("$.products", hasSize(2)))
.andExpect(jsonPath("$.products[*].id", Matchers.containsInAnyOrder(1, 2)))
.andExpect(jsonPath("$.products[*].name", Matchers.containsInAnyOrder("search engine", "adv.")))
.andExpect(jsonPath("$.products[*].company").doesNotExist());

hibernate: deleting entity in a one to many relationship

I have a following error:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`spindledb`.`section`, CONSTRAINT `FK_ftoru9cp83n512p9is8x3vo53` FOREIGN KEY (`scenario_id`) REFERENCES `scenario` (`scenario_id`))
Here are my classes:
Scenario:
#Entity
#Table(name = "scenario")
public class Scenario {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "scenario_id")
private int id;
#Column(name = "title", nullable = false)
private String title;
#NotNull
#DateTimeFormat(pattern = "dd/MM/yyyy")
#Column(name = "creation_date", nullable = false)
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
private LocalDate creationDate;
#ManyToOne
#LazyCollection(LazyCollectionOption.FALSE)
#JoinColumn(name = "id", nullable = false)
private User user;
#OneToMany(mappedBy = "scenario", orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
private Set<Plot> plotList = new HashSet<Plot>();
#OneToMany(mappedBy = "scenario", orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
private Set<Character> characterList = new HashSet<Character>();
#OneToMany(mappedBy = "scenario", cascade=CascadeType.ALL, orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
#OrderBy("sequence ASC")
private Set<Section> sectionList = new HashSet<Section>();
Section:
#Entity
#Table(name = "section")
public class Section {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "section_id")
private int id;
#Size(min = 4, max = 50)
#NotNull
#Column(name = "name")
private String name;
#NotNull
#Column(name = "type")
private String type = SectionType.TEXT.getSectionType();
#Column(name = "visibility")
private boolean visibility;
#NotNull
#Column(name = "sequence")
private int sequence;
#ManyToOne (cascade=CascadeType.ALL)
#LazyCollection(LazyCollectionOption.FALSE)
#JoinColumn(name = "scenario_id", nullable = false)
private Scenario scenario;
Controller:
#RequestMapping(value = { "/delete-{id}-scenario" }, method = RequestMethod.GET)
public String deleteScenario(#PathVariable int id) {
scenarioService.deleteScenarioById(id);
return "redirect:/home";
}
Scenario service:
#Service("scenarioService")
#Transactional
public class ScenarioServiceImpl implements ScenarioService {
#Autowired
private ScenarioDao dao;
#Override
public Scenario findById(int id) {
return dao.findById(id);
}
#Override
public void saveScenario(Scenario scenario) {
dao.saveScenario(scenario);
}
public void updateScenario(Scenario scenario) {
Scenario entity = dao.findById(scenario.getId());
if(entity!=null){
entity.setTitle(scenario.getTitle());
entity.setCreationDate(scenario.getCreationDate());
}
}
#Override
public void deleteScenarioById(int id) {
dao.deleteScenarioById(id);
}
Dao
#Repository("scenarioDao")
public class ScenarioDaoImpl extends AbstractDao<Integer, Scenario> implements ScenarioDao {
#Override
public Scenario findById(int id) {
return getByKey(id);
}
#Override
public void saveScenario(Scenario scenario) {
persist(scenario);
}
#Override
public void deleteScenarioById(int id) {
Query query = getSession().createSQLQuery("delete from scenario where id = :id");
query.setString("id", ""+id);
query.executeUpdate();
}
I understand that the problem is that there may be a Section that can not exist without scenario. Right now however section table in database is empty and I still can't remove Scenario. Thanks for advice
Deleting an entity via Query would bypass any Cascade settings you put via annotation.
I would suggest find the entity first by id, then delete the entity object:
Object scenario = session.load(Scenario.class, id);
if (scenario != null) {
session.delete(scenario);
}
use cascade=CascadeType.ALL with all #ManyToOne relations in class Scenario because if you are going to delete any Scenario from database it must not be referenced any where in data base.
the other way to delete is.
Serializable id = new Long(1); //your id
Object persistentInstance = session.load(Scenario.class, id);
if (persistentInstance != null) {
session.delete(persistentInstance);
}

Categories