@Value("#{applicationProperties['jakas.zmienna.z.pliku.properties']}")
private String zmienna;
Ale adnotacje w tej prezentacji nie są Interfejsami tylko Springowymi, ale też należącymi do JPA i Hibernate.
@Service
public class CustomerServiceImpl {
private final CustomerMapper customerMapper;
private final CustomerDao customerDao;
@Autowired
public CustomerServiceImpl (CustomerMapper mapper, CustomerDao dao) {
this.customerMapper = mapper;
this.customerDao = dao;
}
W internecie poszukać dependency do POMa i podłączyć
Sciągnąć kompletny STS (Spring Tool Suite)
start.spring.io
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication
public class BooksServerApplication {
public static void main(String[] args) {
SpringApplication.run(BooksServerApplication.class, args);
}
}
Wszystkie działają i robią to samo
Wyjątkiem jest @Repository, która dodatkowo konwertuje wyjątki dostawców bazy danych
Adnotacje pozwalają oddzielić od siebie różne warstwy aplikacji
Ułatwiają czytanie i analizowanie kodu
@RequestMapping("/services")
@RestController
public class BooksRestService {
private final BookService bookService;
@Autowired
public BooksRestService(BookService bookService) {
this.bookService = bookService;
}
@RequestMapping(path = "/books", method = RequestMethod.GET)
public List<BookTo> findBooks(BookSearchCriteria bookSearchCriteria) {
return bookService.findBooks(bookSearchCriteria);
}
}
spring-boot-starter-data-jpa
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
spring-boot-starter-web
jar wynikowy (mvn clean install) zawiera jary TomcataRola | Nazwa Warstwy | Odpowiedzialność |
---|---|---|
Komunikacja FrontEnd | REST Service | tłumaczenie requestów |
Use Case | Service | logika biznesowa |
Baza danych | Repository / DAO | persystencja |
A Repository represents all objects of a certain type as a conceptual set. It acts like a collection, except with more elaborate querying capability.[DDD]
REST - REpresentation State Transfer. Roy Fielding 2000.
REST to nie jest architektura, tylko pewien zespół ograniczeń, który jeśli jest zastosowany do architektury to nadaje konkretne role danym, komponentom, hyperlinkom, ...
RESTful - spełniający wszystkie punkty powyższych założeń.
Akcja | Metoda HTTP |
---|---|
CREATE | POST |
RETRIEVE | GET |
UPDATE | PUT |
DELETE | DELETE |
@RequestMapping(path = "/cars", method = RequestMethod.GET)
public List<CarTo> findAllCars() { ... }
@RequestMapping(path = "/car", method = RequestMethod.POST)
public CarTo addCar(@RequestBody CarTo car) { ... }
@RequestMapping(path = "/car", method = RequestMethod.PUT)
public CarTo updateCar(@RequestBody CarTo car) { ... }
@RequestMapping(path = "/car/{id}", method = RequestMethod.DELETE)
public boolean deleteCar(@PathVariable("id") Long id) { ... }
@Service public class CustomerServiceImpl {
private static final String FIND_ALL_LIBRARIES_IN_CITY_SQL =
"SELECT l.id, l.name, l.address_id FROM Library l, Address a
WHERE l.address_id = a.id AND a.city = :city";
@Autowired private NamedParameterJdbcOperations jdbcTemplate;
@Autowired private LibraryRowMapper mapper;
public List<LibraryTO> findAllLibrariesInCity (String cityName) {
SqlParameterSource params = new MapSqlParameterSource("city", cityName);
return jdbcTemplate.query(FIND_ALL_LIBRARIES_IN_CITY_SQL, params, mapper);
}
@Component
public class LibraryRowMapper implements RowMapper<LibraryTO> {
@Override
public LibraryTO mapRow(ResultSet rs, int rowNum) throws SQLException {
LibraryTO library = new LibraryTO();
library.setId(rs.getLong(1));
library.setName(rs.getString(2));
library.setAddress(mapAddressById(rs.getLong(3)));
return library;
}
private AddressTO mapAddressById(Long addressId) {
if (addressId != null && Long.compare(0, addressId) != 0)
return new AddressTO(addressId));
return null;
}
Podobieństwa | Różnice |
---|---|
Klasy i tabele | Szczegółowość |
Właściwości i kolumny | Dziedziczenie (java) |
Instancje i wiersze | Kolekcje (java) |
Identyczność (==, equals vs PK) | |
Nawigacja po grafie obiektów |
Podstawowym zadaniem ORM jest rozwiązanie wrodzonych niezgodności pomiędzy obiektami i bazami danych
public class LibraryEntity {
private String name;
private String domain;
public LibraryEntity () {
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDomain() { return domain; }
public void setDomain(String domain) { this. domain = domain; }
}
@Entity
public class LibraryEntity {
@Id
private Long id;
@Column(name = "name", length = 30, nullable = false)
private String name;
@Column(name = "domain", length = 5, nullable = true)
private String domain;
public LibraryEntity () {
}
// getters and setters
}
@Entity
@Table(name = "LIBRARY", schema = "public")
@Access(AccessType.FIELD)
public class LibraryEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "name", length = 30, nullable = false)
private String name;
@Lob
@Column(nullable = false)
private String description;
private String city;
@Access(AccessType.PROPERTY)
public String getCity () { ... }
public LibraryEntity () { }
}
@Embeddable
public class PersonalData {
private String firstName;
private String lastName;
@Column (columnDefinition=" DATE", nullable = false)
private Date birthDate;
public PersonalData() {
}
// getters & setters
}
@Entity
public class AuthorEntity {
@Embedded
@AttributeOverrides({
@AttributeOverride(
name = "firstName",
column = @Column(
name = "FIRST_NAME",
nullable = false)),
@AttributeOverride(
name = "lastName",
column = @Column(
name = "LAST_NAME",
nullable = false))})
vate PersonalData personalData;
...
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Id
@SequenceGenerator(name = "bookGen", sequenceName = "BOOK_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "bookGen")
private Long id;
@Id
@TableGenerator(
name="bookGen",
table="ID_GEN", // opcjonalnie
pkColumnName="GEN_KEY", // opcjonalnie
valueColumnName="GEN_VALUE", // opcjonalnie
pkColumnValue="BOOK_ID_GEN") // opcjonalnie
@GeneratedValue(strategy = GenerationType.TABLE, generator = "bookGen")
private Long id;
@Entity
@Table(name = "CUSTOMER_CARD")
public class CustomerCardEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false)
private String serialNumber;
@PrePersist
public void generateDefaultSerialNumber() {
serialNumber = new SerialNumberGenerator().generate();
}
}
@Entity
@Table(name = "CUSTOMER_CARD")
@EntityListeners(CustomerCardListener.class)
public class CustomerCardEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false)
private String serialNumber;
}
public class CustomerCardListener {
@PrePersist
public void generateDefaultSerialNumber
(CustomerCardEntity customerCardEntity) {
String serialNumber = new SerialNumberGenerator().generate();
customerCardEntity.setSerialNumber(serialNumber);
}
}
String unitName = "MyPersistenceUnit";
// utwórz
EntityManagerFactory emf = Persistence.createEntityManagerFactory(unitName);
EntityManager em = emf.createEntityManager();
// zrób co masz do zrobienia
zrobCos(em);
// zamknij
em.close();
emf.close();
// zapis
Product banan = new Product(1, "banan", "owoce");
em.persist(banan);
// odczyt
Product bananFromDB = em.find(Product.class, 1);
// usunięcie
Product bananFromDB = em.find(Product.class, 1);
em.remove(bananFromDB);
// zapytanie
Product product = em.createQuery(
"SELECT p FROM Product p WHERE p.category = :cat_param", Product.class)
.setParameter("cat_param", "owoce")
.getSingleResult();
@Entity
public class User {
@OneToOne(
cascade = CascadeType.ALL, // default: empty
fetch = FetchType.LAZY, // default: EAGER
optional = false) // default: true
private Address address;
}
@Entity
public class Address {
}
@Entity
public class User {
@OneToOne
@JoinColumn(name = "ADDRESS_FK")
private Address address;
}
@Entity
public class Address {
@OneToOne(mappedBy = „address”)
private User user;
}
@Entity
public class User {
@OneToMany(
cascade = CascadeType.ALL, // default: empty
fetch = FetchType. EAGER) // default: LAZY
@JoinColumn(name = "user_id")
private Collection<Address> addresses;
}
@Entity
public class Address {
}
@Entity
public class User {
@OneToMany(mappedBy = "user")
private Collection<Address> addresses;
}
@Entity
public class Address {
@ManyToOne
@JoinColumn(name = "ADDRESS_FK", nullable = false)
private User user;
}
@Entity
public class User {
@ManyToMany(
cascade = CascadeType.ALL, // default: empty
fetch = FetchType. LAZY) // default: EAGER
@JoinTable(name = "USER_ADDRESS",
joinColumns = {@JoinColumn(
name = "USER_ID",
nullable = false,
updatable = false)},
inverseJoinColumns = {@JoinColumn(
name = "ADDRESS_ID",
nullable = false,
updatable = false)})
private Collection<Address> addresses;
}
@Entity
public class Address {
@ManyToMany(mappedBy = "user")
private Collection<User> users;
}
@Entity
@Table(name = "BOOK")
public class BookEntity {
@OneToOne(cascade = CascadeType.ALL, mappedBy = "book")
private BookSpoilerEntity bookSpoiler;
}
@Entity
@Table(name = "AUTHOR")
@DiscriminatorColumn(name = "TYPE", length = 6,
discriminatorType = DiscriminatorType.STRING)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class AuthorEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
@Column(nullable = true, length = 30)
protected String nickName;
}
@Entity
@DiscriminatorValue("WRITER")
public class WriterEntity extends AuthorEntity {
@Enumerated(EnumType.STRING)
private LiteraryGenre literaryGenre;
}
@Entity
@DiscriminatorValue("PROFES")
public class ProfessorEntity extends AuthorEntity {
@Column(nullable = true)
private String university;
}
@Entity
@Table(name = "BOOK_EXEMPLAR")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class BookExemplarEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
@Column(nullable = false, length = 15, unique = true)
protected String serialNumber;
}
@Entity
@Table(name = "PAPER_BOOK")
@PrimaryKeyJoinColumn(name = "book_ex_id", referencedColumnName = "id")
public class PaperBookExemplarEntity extends BookExemplarEntity {
private int pagesCount;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private PaperSize paperSize;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private BookCover bookCover;
}
public interface Dao<T> {
void create(T entity);
T get(Serializable id);
T load(Serializable id);
List<T> getAll();
void update(T entity);
void saveOrUpdate(T entity);
void delete(T entity);
void delete(Serializable id);
void deleteAll();
long count();
boolean exists(Serializable id);
}
@Transactional(Transactional.txType.SUPPORTS)
public abstract class AbstractDao<T> implements Dao<T> {
@Autowired
private SessionFactory sessionFactory;
private Class<T> domainClass;
protected Session getSession() {
return sessionFactory.getCurrentSession();
}
@Override
public void create(T entity) {
getSession().save(entity);
}
@Override
@SuppressWarnings("unchecked")
public T get(Serializable id) {
return (T) getSession().get(getDomainClass(), id);
}
@Override
public List<T> getAll() {
return getSession().createQuery("from " + getDomainClassName()).list();
}
}
@Repository
public class LibraryDaoImpl extends AbstractDao<LibraryEntity> {
@SuppressWarnings("unchecked")
public List<LibraryEntity> findByName(String name) {
return getSession()
.createQuery("from LibraryEntity l where l.name like :name")
.setString("name", name + "%")
.list();
}
}
No to jaka właściwie jest różnica między DAO a Repository?
// rozpoczyna transakcję
em.getTransaction().begin();
// wykonanie operacji
Product prodFromDb = em.find(Product.class, 1);
prodFromDb.setCategory("newCategory");
// zatwierdzenie transakcji
em.getTransaction().commit();
// ewentualne wycofanie transakcji
em.getTransaction().rollback();
@Service
@Transactional(readOnly = true)
public class LibraryServiceImpl implements LibraryService {
@Autowired
private LibraryRepository libraryRepository;
@Autowired
private MapperFacade mapper;
@Override
public List<LibraryTO> findAllLibraries () {
List<LibraryEntity> libraries = libraryRepository.findAll();
return mapper.mapAsList(libraries, LibraryTO.class);
}
}
@EnableWebMvc
@ComponentScan("org.itsurvival.books.rest")
public static class BooksRestServiceTestConfiguration {
@Bean
public BookService bookService() {
return Mockito.mock(BookService.class);
}
}
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
private BookService bookService;
@Captor
private ArgumentCaptor<BookSearchCriteria> bookSearchCriteriaCaptor;
@Captor
private ArgumentCaptor<BookTo> bookCaptor;
@Test
public void shouldAddNewBook() throws Exception {
// given
byte[] content = readFileToBytes(
"classpath:org/itsurvival/books/rest/newBook.json");
when(bookService.addBook(any(BookTo.class)))
.thenAnswer(args -> args.getArguments()[0]);
// when
mockMvc.perform(post("/services/book")
.content(content)
.contentType(MediaType.APPLICATION_JSON))
// then
.andExpect(status().isOk())
.andExpect(jsonPath("$.title", is("Test title")));
verify(bookService).addBook(bookCaptor.capture());
BookTo book = bookCaptor.getValue();
assertThat(book.getTitle()).isEqualTo("Test title");
assertThat(book.getAuthor()).isEqualTo("Test author");
assertThat(book.getYear()).isEqualTo(2008);
assertThat(book.getGenre()).isEqualTo(Genre.IT);
assertThat(book.getVersion()).isEqualTo(0L);
assertThat(book.getId()).isNull();
}