I have a spring boot (1.5.4.RELEASE) project using Java 8. I have an entity and it's related domain class like this:
#Entity
#Table(name = "Foo", schema = "dbo")
public class FooEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id")
private int id;
#Column(name="Name")
private String name;
#Column(name="Type")
private String type;
#Column(name="Color")
private String color;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Car")
private Car car;
//getter and setter
}
public class Foo {
private int id;
private String name;
private String type;
private String color;
private Car car;
//Constructors and getters
}
I want to create a repository that fetches this Foo object from the DB but only fetching the complex fields if the user asks for them to prevent unnecessary join statements. The repo looks like this:
import static com.test.entities.QFooEntity.fooEntity;
import static com.test.entities.QCarEntity.carEntity;
#Repository
public class FooRepository {
private final JPAQuery<FooEntity> query = createQuery().from(fooEntity);
public FooRepository getFooByName(String name) {
query.where(fooEntity.name.eq(name));
return this;
}
public FooRepository withCar() {
query.leftJoin(fooEntity.car, carEntity).fetchJoin();
return this;
}
public Foo fetch() {
FooEntity entity = query.fetchOne();
return FooMapper.mapEntityToDomain().apply(entity);
}
}
So a barebones call for a Foo object will return the Entity with values for all the fields except for the car field. If the user wants car information then they have to explicitly call withCar.
Here is the mapper:
public class FooMapper {
public static Function<FooEntity, Foo> mapEntityToDomain() {
return entity -> {
return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), e.getCar());
};
}
}
The problem is when you do e.getCar() if the value is not there (i.e. there's a proxy present) JPA will go out and fetch it for you. I don't want this to be the case. It will just grab the values and map them to the domain equivalent if it's not there then null.
One solution that I've heard (and tried) is calling em.detach(entity); however, this doesn't work as I intended because it throws an exception when you try to access getCar and I've also heard this is not best practice.
So my question is what is the best way to create a repo using a builder pattern on a JPA entity and not have it call the DB when trying to map.
You could create a utility method that will return null if the given object is a proxy and is not initialized:
public static <T> T nullIfNotInitialized(T entity) {
return Hibernate.isInitialized(entity) ? entity : null;
}
Then you can call the method wherever you need it:
return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), nullIfNotInitialized(e.getCar()));
Just map it to a new object and leave out the Car relation, this is the standard approach. You can use MapStruct and just ignore the car field during mapping: http://mapstruct.org/documentation/stable/reference/html/#inverse-mappings
Just don't map the car... Map a field holding the ID and use another method to get the actual Car. I would use a distinctive method name, to differentiate it from the other getters.
class FooEntity {
#Column
private int carId;
public int getCarId() {
return carId;
}
public void setCarId(int id) {
this.carId = id;
}
public Car fetchCar(CarRepository repo) {
return repo.findById(carId);
}
}
You can write query on top of JPA
#Query("select u from Car c")
import org.springframework.data.repository.CrudRepository;
import com.example.model.FluentEntity;
public interface DatabaseEntityRepository extends CrudRepository<FooEntity , int > {
}
As you said
I don't want this to be the case. It will just grab the values and map them to the domain equivalent, if it's not there then null.
Then you just set it to null, because the field car will always not be there.
Otherwise, if you mean not there is that the car not exists in db, for sure a subquery(call the proxy) should be made.
If you want to grab the car when call Foo.getCar().
class Car {
}
class FooEntity {
private Car car;//when call getCar() it will call the proxy.
public Car getCar() {
return car;
}
}
class Foo {
private java.util.function.Supplier<Car> carSupplier;
public void setCar(java.util.function.Supplier<Car> carSupplier) {
this.carSupplier = carSupplier;
}
public Car getCar() {
return carSupplier.get();
}
}
class FooMapper {
public static Function<FooEntity, Foo> mapEntityToDomain() {
return (FooEntity e) -> {
Foo foo = new Foo();
foo.setCar(e::getCar);
return foo;
};
}
}
Make sure you have the db session ,when you call Foo.getCar()
You could try adding state to your repository and influence the mapper. Something like this:
import static com.test.entities.QFooEntity.fooEntity;
import static com.test.entities.QCarEntity.carEntity;
#Repository
public class FooRepository {
private final JPAQuery<FooEntity> query = createQuery().from(fooEntity);
private boolean withCar = false;
public FooRepository getFooByName(String name) {
query.where(fooEntity.name.eq(name));
return this;
}
public FooRepository withCar() {
query.leftJoin(fooEntity.car, carEntity).fetchJoin();
withCar = true;
return this;
}
public Foo fetch() {
FooEntity entity = query.fetchOne();
return FooMapper.mapEntityToDomain(withCar).apply(entity);
}
}
In your mapper, you then include a switch to enable or disable car lookups:
public class FooMapper {
public static Function<FooEntity, Foo> mapEntityToDomain(boolean withCar) {
return e -> {
return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), withCar ? e.getCar() : null);
};
}
}
If you then use new FooRepository().getFooByName("example").fetch() without the withCar() call, e.getCar() should not be evaluated inside FooMapper
You may want to use the PersistentUnitUtil class to query if an attribute of entity object is already loaded or not. Based on that you may skip the call to corresponding getter as shown below. JpaContext you need to supply to user entity bean mapper.
public class FooMapper {
public Function<FooEntity, Foo> mapEntityToDomain(JpaContext context) {
PersistenceUnitUtil putil = obtainPersistentUtilFor(context, FooEntity.class);
return e -> {
return new Foo(
e.getId(),
e.getName(),
e.getType(),
e.getColor(),
putil.isLoaded(e, "car") ? e.getCar() : null);
};
}
private PersistenceUnitUtil obtainPersistentUtilFor(JpaContext context, Class<?> entity) {
return context.getEntityManagerByManagedType(entity)
.getEntityManagerFactory()
.getPersistenceUnitUtil();
}
}
Related
As part of sequence generation for non-primary key, I am using #GeneratorType on entity field. In generator class I require to know a field on that it has been called. Help is appreciated.
#Entity(name = "student")
public class Student {
#GeneratorType(type = IdGenerator.class, when = GenerationTime.INSERT)
private Integer secId;
}
public class IdGenerator implements ValueGenerator<Integer>{
#Override
public Integer generateValue(Session session, Object owner) {
// I want secId here
}
}
You can try to use something like this:
public interface EntityId {
Integer getId();
}
#Entity(name = "student")
public class Student implements EntityId {
#GeneratorType(type = IdGenerator.class, when = GenerationTime.INSERT)
private Integer secId;
#Override
public Integer getId() {
return secId;
}
// ...
}
#Entity
public class OtherEntity implements EntityId {
// ...
}
public class IdGenerator implements ValueGenerator<Integer> {
#Override
public Integer generateValue(Session session, Object owner) {
if (!(owner instanceof EntityId)) {
throw new IllegalArgumentException("IdGenerator can be used only with entities that implement EntityId interface");
}
// I want secId here
Integer id = ((EntityId) owner).getId();
}
}
If you use Java 14 or higher you can use pattern matching for instanceof as more elegant construction.
public class IdGenerator implements ValueGenerator<Integer> {
#Override
public Integer generateValue(Session session, Object owner) {
if (owner instanceof EntityId entityId) {
// I want secId here
Integer id = entityId.getId();
// ...
} else {
throw new IllegalArgumentException("IdGenerator can be used only with entities that implement EntityId interface");
}
}
}
I have a problem with the hibernate entity, and I would like to know if it is something I overlooked or if it is a bug in IntelliJ IDEA.
I have a Value object bank account:
class BankAccount
{
private String value;
public BankAccount(String value) {
// validation
this.value;
}
}
Which has defined it's own hibernate type:
public class BankAccountType extends AbstractSingleColumnStandardBasicType<BankAccount> {
public static final BankAccountType INSTANCE = new BankAccountType();
public static final String NAME = "bankAccount";
public BankAccountType() {
super(LongVarcharTypeDescriptor.INSTANCE, BankAccountTypeDescriptor.INSTANCE);
}
#Override
public String getName() {
return null;
}
}
And I have an entity:
#Entity
#TypeDefs({
#TypeDef(
name = BankAccountType.NAME,
typeClass = BankAccountType.class,
defaultForType = BankAccount.class
)
})
class User {
private UUID id;
//...
#Column
private BankAccount bankAccount;
//...
}
It works perfectly, but IDEA keeps telling me 'Basic attribute should not be BankAccount.'
Is there any way, how to get rid of this error without changing my entities? Is it a good idea to use value objects as a column in my entities?
Thanks a lot!
I have a function createObject() in my service rest Service:
#Service
public class MyService {
//Repos and contructor
#Transactional
public ObjectDto createObject(Object) {
Mother mother = new Mother(name, age);
Kid kid = new Kid(name, age);
mother.addKid(kid);
this.motherRepo.saveAndFlush(mother);
Long kidId = kid.getId();
doStuffWithKidId();
return new ObjectDto()
.withMother(mother)
.withKid(kid)
.build();
}
}
My entity for mother/kid is basically like this:
#Entity
#Table("mother")
public class mother() {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id)
private Long id;
//other attributes, including #OneToMany for Kid
//Getter/Setter
}
There is a similar Entity for Kid.
As you can see, the id is set by the database. There is no setter for id in the entity. The constructor is without id as well.
Now I want to test my service. I mock my repos and want to verify, that my ObjectDto contains the values, like id.
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
public MyServiceTest {
#Mock
private MotherRepo motherRepo;
#InjectMocks
private MyService myService;
#Test
void createObjectTest() {
ObjectDto expectedObjectDto = setup...;
Object inputObject = setup...;
assertThat.(this.myService.createObject(inputObject))
.isEqualToComparingFieldByField(expectedObjectDto);
}
}
The expected ObjectDto looks something like
{
"motherId":1,
"motherAge":40,
"kidId":1
...
}
The problem is, that the id is set up by the database. Since there is no database and the repository is mock with Mockito, this value is always null. Even if I set my expectedObjectDto with null as id, I need the id in the "doStuffWithKidId()" in the service. Atm I get a NullPointerException.
Is there a possibility to set the id, like with ReflectionTestUtils.setField()? In literature I read, that a service should always be tested using mocks. Is this correct or do I need a in-memory db like H2?
Thanks for the help.
Use doAnswer...
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Java6Assertions.assertThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
#RunWith(MockitoJUnitRunner.class)
public class MockitoSettingDatabaseIds {
private static class TestEntity {
private long id;
private String text;
public TestEntity(String text) {
this.text = text;
}
public long getId() {
return id;
}
public String getText() {
return text;
}
}
private interface TestEntityDAO {
void save(TestEntity entity);
}
private static long someLogicToTest(TestEntityDAO dao, TestEntity entity) {
dao.save(entity);
return entity.getId();
}
#Test
public void shouldReturnDatabaseGeneratedId() {
long expectedId = 12345L;
TestEntityDAO dao = mock(TestEntityDAO.class);
TestEntity entity = new TestEntity("[MESSAGE]");
doAnswer(invocation -> {
ReflectionTestUtils.setField((TestEntity) invocation.getArgument(0), "id", expectedId);
return null;
}).when(dao).save(entity);
assertThat(someLogicToTest(dao, entity)).isEqualTo(expectedId);
}
}
To answer your comment, just do the same thing to the Kid collection, for example...
doAnswer(invocation -> {
Mother toSave = (Mother) invocation.getArgument(0);
ReflectionTestUtils.setField(toSave, "id", expectedId);
for (int k = 0; k < toSave.getKids().size(); k++) {
ReflectionTestUtils.setField(toSave.getKids().get(k), "id", expectedId + k + 1);
}
return null;
}).when(dao).save(entity);
This will set the id of the Mother to expectedId and the IDs of the Kids to expectedId + 1, expectedId + 2 etc.
i have problem with saving data in DB.I'm new in Spring Boot. When i run my program the result of writen data is: packagename#randomcode example:com.abc.patient.Patient#6e3e681e
This is my Entity class - Patient.java
#Entity
public class Patient {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
// getter, setter, constructor, etc
}
This is my CrudRepo PatientRepository.java
public interface PatientRepository extends CrudRepository<Patient,Integer> {
}
This is my Service class PatientService.java
#Service
public class PatientService {
#Autowired
private PatientRepository patientRepository;
public void savePatient (String name) {
Patient patient = new Patient(name);
patientRepository.save(patient);
}
public Optional<Patient> showPatient(int id) {
return patientRepository.findById(id);
}
public List<Patient> showAllPatients() {
List<Patient> patients = new ArrayList<>();
patientRepository.findAll().forEach(patients::add);
return patients;
}
}
I think that problem in in the savePatient method in this line:
Patient patients = new Patient(name);
I checked the "name" parameter and it's in 100% correct String. I'm using Derby DB.
The only problem you have is how you are printing out your Patient class. Define a proper toString() or just debug yourself to see the resulting fields. There is no problem in your JPA implementation.
See this question for the details of default toString
Try:
public void savePatient(Patient patient) {
patientRepository.save(patient);
}
I've been following a lot of tutorial on how to get a list of result by referencing a specific column in the table.
I have this table.
I want to get the list of result with a plan_code "TEST123"
This is my code:
PlanRepository.java
public interface PlanCoverageRepository extends CrudRepository<PlanCoverage, Long> {
List<PlanCoverage> findAllByPlan_code(String plan_code);
}
PlanCoverageService.java
public interface PlanCoverageService {
public List<PlanCoverage> getAllPlanCoverageByPlanCode(String plan_code);
}
PlanCoverageServiceImpl.java
#Service
#Transactional
public class PlanCoverageServiceImpl implements PlanCoverageService {
#Override
public List<PlanCoverage> getAllPlanCoverageByPlanCode(String plan_code) {
return (List<PlanCoverage>) planCoverageRepository.findAllByPlan_code(plan_code);
}
}
PlanCoverageController.java
#Controller
#RequestMapping(value="/admin")
public class PlanCoverageController {
#Autowired
PlanCoverageService planCoverageService;
#RequestMapping(value="/Test/{plan_code}", method=RequestMethod.GET)
public ModelAndView test(#PathVariable String plan_code) {
ModelAndView model = new ModelAndView();
PlanCoverage planCoverage = (PlanCoverage) planCoverageService.getAllPlanCoverageByPlanCode(plan_code);
model.addObject("planCoverageForm",planCoverage);
model.setViewName("plan_coverage_form");
return model;
}
}
PlanCoverage.java
#Entity
#Table(name="plan_coverage")
public class PlanCoverage {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private long coverage_id;
#Column(name="plan_code")
private String plan_code;
#Column(name="coverage_description")
private String coverage_description;
/..getters and setters
#ManyToOne()
#JoinColumn(name="plan_code", referencedColumnName = "plan_code",insertable=false, updatable=false)
private Plan plan;
public Plan getPlan() {
return plan;
}
public void setPlan(Plan plan) {
this.plan = plan;
}
}
Please help me. I've been stuck with these for a few days and non of the tutorials seems to work on me. Thank you so much!!
You have messed up with the convention that spring boot is using to compose query methods. The case of the fields in the entity should follow the lower camel-case scheme, like so:
#Column(name="plan_code")
private String planCode;
and then the query method in PlanCoverageRepository should be:
List<PlanCoverage> findAllByPlanCode(String planCode);