Jak poradzić sobie z nullami przy tworzeniu dto?

0

Klasa ClientDto wygląda tak:

public final class ClientDto {

	private Long id;
	private String login;
	private String nip;
	private String companyName;

	private String contactAddressCity;
	private String contactAddressStreet;
	private String contactAddressHouseNumberEtc;
	private String contactAddressPostalCode;

	private String registerAddressCity;
	private String registerAddressStreet;
	private String registerAddressHouseNumberEtc;
	private String registerAddressPostalCode;
// ...

jest tworzona z Client w której adresy nie muszą być podane, więc pojawia się nullpointerexception gdy mapuję dto tak:

@Override
	public ClientDto clientToDto(Client client) {
		ClientDto clientDto = new ClientDto();
		clientDto.setId(client.getId())
			.setLogin(client.getUser().getLogin())
			.setCompanyName(client.getCompanyName())
			.setContactAddressCity(client.getContactAddress().city)
			.setContactAddressHouseNumberEtc(client.getContactAddress().houseNumberEtc)
			.setContactAddressPostalCode(client.getContactAddress().postalCode)
			.setContactAddressStreet(client.getContactAddress().street)
			.setNip(client.getNip())
			.setRegisterAddressCity(client.getRegisterAddress().city)
			.setRegisterAddressHouseNumberEtc(client.getRegisterAddress().houseNumberEtc)
			.setRegisterAddressPostalCode(client.getRegisterAddress().postalCode)
			.setRegisterAddressStreet(client.getRegisterAddress().street);
		return clientDto;
	}

np. jak nie ma adresu to powinienem dostać jsona:

{
        "id": 2,
        "login": null,
        "nip": "9510525054",
        "companyName": "Janusz Soft sp. z o.o.",
        "contactAddressCity": null,
        "contactAddressStreet": null,
        "contactAddressHouseNumberEtc": null,
        "contactAddressPostalCode": null,
        "registerAddressCity": null,
        "registerAddressStreet": null,
        "registerAddressHouseNumberEtc": null,
        "registerAddressPostalCode": null
    }

Jak to sprytniej obsłużyć niż

@Override
    public ClientDto clientToDto(Client client) {
        ClientDto clientDto = new ClientDto();
        clientDto.setId(client.getId())
            .setLogin(client.getUser().getLogin())
            .setCompanyName(client.getCompanyName())
            .setNip(client.getNip());

             if(client.getContactAddress == null) {
                clientDto.setContactAddressCity(client.getContactAddress().city)
                    .setContactAddressHouseNumberEtc(client.getContactAddress().houseNumberEtc)
                    .setContactAddressPostalCode(client.getContactAddress().postalCode)
                    .setContactAddressStreet(client.getContactAddress().street)
             }
           
            if(client.getRegisterAddress == null) {
                clientDto.setRegisterAddressCity(client.getRegisterAddress().city)
                    .setRegisterAddressHouseNumberEtc(client.getRegisterAddress().houseNumberEtc)
                    .setRegisterAddressPostalCode(client.getRegisterAddress().postalCode)
                    .setRegisterAddressStreet(client.getRegisterAddress().street);
            }
        return clientDto;
    }

. W Encji jak dam Optionale do getterozy to mi się nic nie rozwali? A co jak mam publiczne metody, mam je robić Optionalami?
Krew mnie zalewa od tego JPA. Po 5 razy przepisuje się to samo.

1

Ja bym zrobił np, tak...
Dodaj sobie jakiś GenericConverter, np.:

public interface GenericConverter<S,T>{

T getTarget(S s);

S getSource(T t);

A potem dodaj do niego implementację:

ClientConverter implements GenericConverter <Client, ClientDto> {
...
}

Potem te dane adresowe wyciągnij do jakiegoś AddressDto bo trochę wydaje mi się bezsensu trzymanie tego w Stringach. Jak potrzebujesz mieć to w JSONie bez zagnieżdżeń to jest na to adnotacja tylko teraz nie pamiętam. Spróbuj googlnąć a jak nie będziesz miał to poszukam.

Dodasz sobie też konwerter do tego adresu i konwerter zanim zacznie konwertować będzie sprawdzał czy przekazany obiekt nie jest null'em. Jeśli jest null to też zwróć null. Potem w tym ClientGenericConverter wykorzystaj też AddressConverter i wszystko powinno śmigać.

0

Jeżeli już korzystasz z JPA to możesz zwracać dto od razu z zapytania.

https://stackoverflow.com/questions/40218903/hibernate-hql-multiple-new-objects

1

Najlepiej to w Kotlinie a jak Java to pozostaje płakać i sprawdzać nulle z ręki, ew. Optionalem możesz użyć Optional.ofNullable(client.getAddress()). gdybyś zrobił sobie klasę AddressDto i użył jej w ClientDtoo dla obu adresów, to też byłoby łatwiej.

0

A czemu to DTO takie płaskie?

1

Spłaszczenie Jsona o ktorym pisałem mozesz zrobic dodajac adnotacje: @JsonUnwrapped

0

a dto też testujecie jednostkowymi? Szału dostanę z tym pisaniem assertEquals(x.getValue(), y.setValue(x.getValue()).getValue());

1

Przecież to proste, ja robie taki test:

var systemik  = wszechświat.postawMójSystemik();
var walniętySystem = systemik.walnijMuTakiegoPOSTa(ŁoRanyJakiPost);
var klientDtoJakoJson  = walniętySystem .weźNoGETemKlientaNr(2);
assertEquals( oczekiwanyJson, klientDtoJakoJson  );
0
jarekr000000 napisał(a):

Przecież to proste, ja robie taki test:

var systemik  = wszechświat.postawMójSystemik();
var walniętySystem = systemik.walnijMuTakiegoPOSTa(ŁoRanyJakiPost);
var klientDtoJakoJson  = walniętySystem .weźNoGETemKlientaNr(2);
assertEquals( oczekiwanyJson, klientDtoJakoJson  );

ta... 70 linijek jak w mordę strzelił

public class AddressMapperTest {

	GenericMapper<Address, AddressDto> mapper;
	
	@Before
	public void setUp() {
		this.mapper = new AddressMapper();
	}
	
	@Test
	public void testNull() {
		// given
		Address address = null;
		AddressDto dto = null;
		
		// when
		AddressDto dtoFromAddress = mapper.sourceToDto(address);
		Address addressFromDto = mapper.dtoToNewSource(dto);
		Address addressFromDtoAndAdress = mapper.dtoToUpdatedSource(address, dto);
		
		// then
		assertNull(dtoFromAddress);
		assertNull(addressFromDto);
		assertNull(addressFromDtoAndAdress);
	}
	
	@Test
	public void testEmpty() {
		// given
		AddressDto dto = new AddressDto();
		
		// when
		Address addressFromDto = mapper.dtoToNewSource(dto);
		
		// then
		assertNotNull(addressFromDto);
	}
	
	@Test
	public void testSourceToDto() {
		// given
		Address address = new Address("a", "b", "c", "00-001");
		
		// when
		AddressDto dto = mapper.sourceToDto(address);
		
		// then
		assertEquals("a", dto.getCity());
		assertEquals("b", dto.getStreet());
		assertEquals("c", dto.getHouseNumberEtc());
		assertEquals("00-001", dto.getPostalCode());
	}
	
	@Test
	public void testDtoToNewSource() {
		// given
		AddressDto dto = new AddressDto();
		dto.setCity("a").setHouseNumberEtc("1b").setStreet("c").setPostalCode("00-001");
		
		// when
		Address addressFromDto = mapper.dtoToNewSource(dto);
		
		// then
		assertEquals("a", addressFromDto.city);
		assertEquals("1b", addressFromDto.houseNumberEtc);
		assertEquals("c", addressFromDto.street);
		assertEquals("00-001", addressFromDto.postalCode);
	}
	
	@Test
	public void testDtoToUpdatedSource() {
		testDtoToNewSource();
	}

}
1

Zdziwiony? Przecież zrobiłeś dokładnie inaczej niż pisalem.
Tłumaczę raz jeszcze:

assertEquals( oczekiwanyJson, klientDtoJakoJson  )

czyli

assertEquals( """{ "city" : "a", "street": "b", "houseNumberEtc":"c" ... .} """, klientDtoJakoJson  )

Jak masz mappery to najłatwiej testować całościowo.

Jeszcze jeden trik.

assertEquals( expectedDto, mapper(dbObject))

O ile tylko dto ma equals (a to proste).

1

Na bootcampie to my używamy MapStruct'a.

0

Nazywaj testy jakoś sensownie...

Julian_ napisał(a):
	public void testEmpty() {
		// given
		AddressDto dto = new AddressDto();
		
		// when
		Address addressFromDto = mapper.dtoToNewSource(dto);
		
		// then
		assertNotNull(addressFromDto);
	}

Co to to ma być?
Abstrachując od nazwy która nie ma sensu to sprawdzasz czy nie jest nullem a nie czy jest pusty.

Po drugie jak masz niżej taki test:

Julian_ napisał(a):
	public void testDtoToNewSource() {
		// given
		AddressDto dto = new AddressDto();
		dto.setCity("a").setHouseNumberEtc("1b").setStreet("c").setPostalCode("00-001");
		
		// when
		Address addressFromDto = mapper.dtoToNewSource(dto);
		
		// then
		assertEquals("a", addressFromDto.city);
		assertEquals("1b", addressFromDto.houseNumberEtc);
		assertEquals("c", addressFromDto.street);
		assertEquals("00-001", addressFromDto.postalCode);
	}

To po kiego Ci ten pierwszy? Ten drugi jak się wykona to zrobi w zasadzie to samo co ten pierwszy plus kilka dodatkowych rzeczy. Wywal ten pierwszy i będziesz miał mniej testów do utrzymania bo jak ich narobisz kilkaset to potem boli jak trzeba tyle zmieniać.

Julian_ napisał(a):
         assertEquals("a", addressFromDto.city);
  assertEquals("1b", addressFromDto.houseNumberEtc);
  assertEquals("c", addressFromDto.street);
  assertEquals("00-001", addressFromDto.postalCode);

Z dostępem do tych pól na pewno wszystko jest okej?

Julian_ napisał(a):
 @Test
  public void testDtoToUpdatedSource() {
    testDtoToNewSource();
  }

WAT :o ?!

2

Odnośnie zwracania dto prosto z zapytania to można to zrobić mniej więcej tak (zupełnie zgaduję jakie masz mapowanie):

        Query query = session.createQuery("SELECT new your.package.ClientDto(c.id, c.login, c.nip, c.companyName, a1.city, a1.street, a2.city, a2.street) "
            + " FROM Client c "
            + " LEFT JOIN c.contactAddress a1"
            + " LEFT JOIN c.registerAddress a2");
        List<ClientDto> result = query.getResultList();

Do tego potrzebujesz odpowiedni konstruktor w ClientDto, np. coś w rodzaju:

		public ClientDto(String id, String login, String companyName, String city1, String street1, String city2, String street2) {
			this.id = id;
			this.login = login;
			this.companyName = companyName;
			this.contactAddress = new AddressDto(city1, street1);
			this.registerAddress = new AddressDto(city2, street2);
		}
0
eL napisał(a):

Nazywaj testy jakoś sensownie...

Julian_ napisał(a):
```java @Test
public void testEmpty() {
	// given
	AddressDto dto = new AddressDto();
	
	// when
	Address addressFromDto = mapper.dtoToNewSource(dto);
	
	// then
	assertNotNull(addressFromDto);
}

Co to to ma być?
Abstrachując od nazwy która nie ma sensu to sprawdzasz czy nie jest nullem a nie czy jest pusty.

Po drugie jak masz niżej taki test:

Julian_ napisał(a):
```java@Test
public void testDtoToNewSource() {
	// given
	AddressDto dto = new AddressDto();
	dto.setCity("a").setHouseNumberEtc("1b").setStreet("c").setPostalCode("00-001");
	
	// when
	Address addressFromDto = mapper.dtoToNewSource(dto);
	
	// then
	assertEquals("a", addressFromDto.city);
	assertEquals("1b", addressFromDto.houseNumberEtc);
	assertEquals("c", addressFromDto.street);
	assertEquals("00-001", addressFromDto.postalCode);
}

To po kiego Ci ten pierwszy? Ten drugi jak się wykona to zrobi w zasadzie to samo co ten pierwszy plus kilka dodatkowych rzeczy. Wywal ten pierwszy i będziesz miał mniej testów do utrzymania bo jak ich narobisz kilkaset to potem boli jak trzeba tyle zmieniać.

w testEmpty sprawdzam czy da się przekonwertować obiekt z pustymi wartościami, jesteś pewien, że wywalić?

Julian_ napisał(a):
         assertEquals("a", addressFromDto.city);
	assertEquals("1b", addressFromDto.houseNumberEtc);
	assertEquals("c", addressFromDto.street);
	assertEquals("00-001", addressFromDto.postalCode);

Z dostępem do tych pól na pewno wszystko jest okej?

klasa Address wygląda tak o:

@Entity
@Immutable
@Table(uniqueConstraints = @UniqueConstraint(columnNames = { "city", "street", "houseNumberEtc", "postalcode" }))
public class Address {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;

	@Column(updatable = false)
	public final String city;

	@Column(updatable = false)
	public final String street;

	@Column(updatable = false)
	public final String houseNumberEtc;

	@Pattern(regexp = "^\\d{2}-\\d{3}$")
	@Column(updatable = false)
	public final String postalCode;
	
	@OneToOne(mappedBy="address")
	private Location area;

	/**
	 * Arguments of the class are immutable but JPA always needs a default
	 * constructor. Documentation of the Hibernate says: "The entity class must have
	 * a public or protected no-argument constructor."
	 */
	protected Address() {
		this.city = null;
		this.street = null;
		this.houseNumberEtc = null;
		this.postalCode = null;
	}

	public Address(String city, String street, String houseNumberEtc, String postalCode) {
		this.city = city;
		this.street = street;
		this.houseNumberEtc = houseNumberEtc;
		this.postalCode = postalCode;
	}

	public Long getId() {
		return id;
	}
	
	@Override
	public String toString() {
		return "Address [" + city + street + houseNumberEtc + postalCode + "]";
	}
}
0

Napisz sobie makro w Scali i wtedy sobie zaszyjesz w nim dowolne reguły a nawet obsługę własnych adnotacji.

0

Tak poza tematem. Tworzenie mapperów wymaga tworzenia getterów, co za tym idzie podobnej struktury wewnętrznej obiektów. Czy nie jest to trochę gwałt na OOP? Czy nie lepiej żeby obiekt umiał sam się przetransferować do DTO, bez informowania świata o swojej implementacji?
Matka rodzi dziecko sama, lekarz nie wyrywa kawałków płodu i nie składa potem tego w coś żywego.

0
danek napisał(a):

Tak poza tematem. Tworzenie mapperów wymaga tworzenia getterów, co za tym idzie podobnej struktury wewnętrznej obiektów. Czy nie jest to trochę gwałt na OOP? Czy nie lepiej żeby obiekt umiał sam się przetransferować do DTO, bez informowania świata o swojej implementacji?
Matka rodzi dziecko sama, lekarz nie wyrywa kawałków płodu i nie składa potem tego w coś żywego.

Tylko co jeśli dto ma być okrojoną wersją domeny?

Obczaj ile mam zmiennych: https://github.com/trolololololo4/bellaputanesca/blob/master/bella-core/src/main/java/com/julian/deliverp/domain/Client.java

a pokazuję tlyko te: https://github.com/trolololololo4/bellaputanesca/blob/master/bella-core/src/main/java/com/julian/deliverp/api/dto/ClientDto.java

1

Może użycie czegoś do pomocy? na przykład
http://mapstruct.org/

Widzę że już się pojawił ten mapStruct wcześniej i skupiłem się na ostatnim poście a nie problemie z początku.
Widzę że przeszedłeś z totalnie płaskiej struktury na Dto z Dtosami, to może coś w stylu

addressDto = Optional.ofNullable(address).map(AddressMapper::toDto);

Ten Optional tworzony tak dla przykładu, możesz też zawsze sobie zwracać optionala, albo po prostu w metodzie mapującej sprawdzać czy argument nie jest nullem

1 użytkowników online, w tym zalogowanych: 0, gości: 1