git config user.name NAME
git config --global user.name NAME
git config --global user.email EMAIL
git config --global credential.helper store
git add FILE
git add DIRECTORY
git add .
git commit -m "MESSAGE"
git push REMOTE BRANCH
git fetch REMOTE
git pull REMOTE
git branch
git branch -r
git branch BRANCH
git branch -d BRANCH
git checkout EXISTING_BRANCH
git checkout -b NEW_BRANCH
@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) { ... }
@RequestMapping(path = "/car", method = RequestMethod.PUT)
public CarTo updateCar(@RequestBody CarTo car) {
// co jeśli Spring nie dostanie jako argument JSONa poprawnie -
// ale biznesowo poprawnie - tłumaczącego się na CarTo?
}
@RequestMapping(path = "/car", method = RequestMethod.PUT)
public CarTo updateCar(@RequestBody @Valid CarTo car) {
// Metoda nie zostanie wywołana jesli walidacje okreslone
// deklaratywnie w CarTo nie przechodzą
}
public class CarTo {
private long id;
@NotNull
@Size(min = 5, max = 200)
private String name;
@NotNull
private Status status;
@NotNull
private Condition condition;
}
@RestControllerAdvice
public class CarControllerAdvice {
@ExceptionHandler({ MethodArgumentNotValidException.class })
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public List<ErrorMessageTo> validationException(MethodArgumentNotValidException ex) {
ErrorMessageTo message = new ErrorMessageTo();
message.setMessage(ex.getLocalizedMessage());
return Lists.newArrayList(message);
}
}
@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;
}
@Mappings({
@Mapping(source = "orders", target = "orderItems"),
@Mapping(source = "customerName", target = "name")
})
Customer toCustomer(CustomerDto customerDto);
LINK
@Mappings({
@Mapping(target = "fish.kind", source = "fish.type"),
@Mapping(target = "fish.name", ignore = true),
@Mapping(target = "ornament", source = "interior.ornament"),
@Mapping(target = "material.materialType", source = "material"),
@Mapping(target = "quality.report.organisation.name", source = "quality.report.organisationName")
})
FishTankDto map(FishTank source);
LINK
@Repository
@Transactional(readOnly = true)
class AccountDaoJpaImpl implements AccountDao {
@PersistenceContext
private EntityManager em;
@Override
@Transactional
public Account save(Account account) {
if (account.getId() == null) {
em.persist(account);
return account;
} else {
return em.merge(account);
}
}
@Override
public List<Account> findByCustomer(Customer customer) {
TypedQuery query = em.createQuery("select a from Account a where a.customer = ?1", Account.class);
query.setParameter(1, customer);
return query.getResultList();
}
}
public interface AccountRepository extends
JpaRepository<Account, Long> {
List<Account> findByCustomer(Customer customer);
}
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
}
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
User findByLastnameOrFirstname(@Param("lastname") String lastname, @Param("firstname") String firstname);
@Modifying
@Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);
}
@PersistenceContext
private EntityManager em;
JPAQueryFactory queryFactory = new JPAQueryFactory(this.em);
// Metoda
QCustomer customer = QCustomer.customer;
Customer result = queryFactory.selectFrom(customer)
.where(customer.firstName.eq("Bob"),
customer.lastName.eq("Wilson"))
.fetchOne();
QCustomer customer = QCustomer.customer;
List<Customer> results = queryFactory.selectFrom(customer)
.orderBy(customer.lastName.asc(), customer.firstName.desc())
.fetch();
List<Customer> results = queryFactory.select(customer.lastName).from(customer)
.groupBy(customer.lastName)
.fetch();
QCustomer customer = QCustomer.customer;
// delete all customers
queryFactory.delete(customer).execute();
// delete all customers with a level less than 3
queryFactory.delete(customer).where(customer.level.lt(3)).execute();
QLegoSet legoSet = QLegoSet.legoSet;
BooleanExpression ex = legoSet.legoName.eq("");
ex = ex.and(legoSet.legoName.contains("asdf"));
ex = ex.and(legoSet.legoVersion.isNull());
QLegoSet legoSet = QLegoSet.legoSet;
BooleanBuilder builder = new BooleanBuilder();
if (status != null) {
builder.and(legoSet.legoStatus.eq(status));
}
if (StringUtils.isNotBlank(externallId)) {
builder.and(legoSet.legoName.eq(externallId));
}
Predicate p = builder.getValue();
# H2 Web Console (H2ConsoleProperties)
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.h2.console.settings.trace=false
spring.h2.console.settings.web-allow-others=true
// 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();
}