From e311a2450daa1479c9ab15fa496dc80eeab18f17 Mon Sep 17 00:00:00 2001 From: Vyaceslav Sv Date: Mon, 4 Jan 2021 22:37:57 +0300 Subject: [PATCH 1/6] created crud class --- 1.txt | 1 + full.sql | 135 +++++++++++++++ hibernate-app.iml | 14 ++ hibernate.html | 161 ++++++++++++++++++ lifecycle.jpg | Bin 0 -> 123897 bytes pom.xml | 44 +++++ .../flamexander/hibernate/PrepareDataApp.java | 37 ++++ .../flamexander/hibernate/crud/Product.java | 61 +++++++ .../hibernate/crud/ProductDao.java | 107 ++++++++++++ .../hibernate/crud/RollbackApp.java | 35 ++++ src/main/resources/META-INF/persistence.xml | 19 +++ .../configs/cascade/hibernate.cfg.xml | 19 +++ .../configs/composed_key/hibernate.cfg.xml | 19 +++ .../configs/criteria_api/hibernate.cfg.xml | 18 ++ .../resources/configs/crud/hibernate.cfg.xml | 19 +++ src/main/resources/configs/hibernate.cfg.xml | 16 ++ .../table_per_class/hibernate.cfg.xml | 20 +++ .../configs/lifecycle/hibernate.cfg.xml | 18 ++ .../configs/linked_objects/hibernate.cfg.xml | 19 +++ .../configs/locking/hibernate.cfg.xml | 18 ++ .../configs/many_to_many/hibernate.cfg.xml | 19 +++ .../one_to_many_and_back/hibernate.cfg.xml | 19 +++ .../configs/one_to_one/hibernate.cfg.xml | 19 +++ .../configs/validation/hibernate.cfg.xml | 18 ++ src/main/resources/hibernate.properties | 7 + target/classes/META-INF/persistence.xml | 19 +++ .../classes/configs/cascade/hibernate.cfg.xml | 19 +++ .../configs/composed_key/hibernate.cfg.xml | 19 +++ .../configs/criteria_api/hibernate.cfg.xml | 18 ++ target/classes/configs/crud/hibernate.cfg.xml | 19 +++ target/classes/configs/hibernate.cfg.xml | 16 ++ .../table_per_class/hibernate.cfg.xml | 20 +++ .../configs/lifecycle/hibernate.cfg.xml | 18 ++ .../configs/linked_objects/hibernate.cfg.xml | 19 +++ .../classes/configs/locking/hibernate.cfg.xml | 18 ++ .../configs/many_to_many/hibernate.cfg.xml | 19 +++ .../one_to_many_and_back/hibernate.cfg.xml | 19 +++ .../configs/one_to_one/hibernate.cfg.xml | 19 +++ .../configs/validation/hibernate.cfg.xml | 18 ++ target/classes/hibernate.properties | 7 + 40 files changed, 1129 insertions(+) create mode 100644 1.txt create mode 100644 full.sql create mode 100644 hibernate-app.iml create mode 100644 hibernate.html create mode 100644 lifecycle.jpg create mode 100644 pom.xml create mode 100644 src/main/java/com/flamexander/hibernate/PrepareDataApp.java create mode 100644 src/main/java/com/flamexander/hibernate/crud/Product.java create mode 100644 src/main/java/com/flamexander/hibernate/crud/ProductDao.java create mode 100644 src/main/java/com/flamexander/hibernate/crud/RollbackApp.java create mode 100644 src/main/resources/META-INF/persistence.xml create mode 100644 src/main/resources/configs/cascade/hibernate.cfg.xml create mode 100644 src/main/resources/configs/composed_key/hibernate.cfg.xml create mode 100644 src/main/resources/configs/criteria_api/hibernate.cfg.xml create mode 100644 src/main/resources/configs/crud/hibernate.cfg.xml create mode 100644 src/main/resources/configs/hibernate.cfg.xml create mode 100644 src/main/resources/configs/inheritance/table_per_class/hibernate.cfg.xml create mode 100644 src/main/resources/configs/lifecycle/hibernate.cfg.xml create mode 100644 src/main/resources/configs/linked_objects/hibernate.cfg.xml create mode 100644 src/main/resources/configs/locking/hibernate.cfg.xml create mode 100644 src/main/resources/configs/many_to_many/hibernate.cfg.xml create mode 100644 src/main/resources/configs/one_to_many_and_back/hibernate.cfg.xml create mode 100644 src/main/resources/configs/one_to_one/hibernate.cfg.xml create mode 100644 src/main/resources/configs/validation/hibernate.cfg.xml create mode 100644 src/main/resources/hibernate.properties create mode 100644 target/classes/META-INF/persistence.xml create mode 100644 target/classes/configs/cascade/hibernate.cfg.xml create mode 100644 target/classes/configs/composed_key/hibernate.cfg.xml create mode 100644 target/classes/configs/criteria_api/hibernate.cfg.xml create mode 100644 target/classes/configs/crud/hibernate.cfg.xml create mode 100644 target/classes/configs/hibernate.cfg.xml create mode 100644 target/classes/configs/inheritance/table_per_class/hibernate.cfg.xml create mode 100644 target/classes/configs/lifecycle/hibernate.cfg.xml create mode 100644 target/classes/configs/linked_objects/hibernate.cfg.xml create mode 100644 target/classes/configs/locking/hibernate.cfg.xml create mode 100644 target/classes/configs/many_to_many/hibernate.cfg.xml create mode 100644 target/classes/configs/one_to_many_and_back/hibernate.cfg.xml create mode 100644 target/classes/configs/one_to_one/hibernate.cfg.xml create mode 100644 target/classes/configs/validation/hibernate.cfg.xml create mode 100644 target/classes/hibernate.properties diff --git a/1.txt b/1.txt new file mode 100644 index 0000000..507945c --- /dev/null +++ b/1.txt @@ -0,0 +1 @@ +orphanRemoval \ No newline at end of file diff --git a/full.sql b/full.sql new file mode 100644 index 0000000..893ce2f --- /dev/null +++ b/full.sql @@ -0,0 +1,135 @@ +BEGIN; + +DROP TABLE IF EXISTS employees_details CASCADE; +CREATE TABLE employees_details (id bigserial PRIMARY KEY, email VARCHAR(255), city varchar(255)); +INSERT INTO employees_details (email, city) VALUES +('terminator@gmail.com', 'California'), +('rembo@gmail.com', 'Atlanta'), +('corben_dallas@gmail.com', 'New York'); + +DROP TABLE IF EXISTS employees CASCADE; +CREATE TABLE employees (id bigserial PRIMARY KEY, name VARCHAR(255), details_id bigint, FOREIGN KEY (details_id) REFERENCES employees_details (id)); +INSERT INTO employees (name, details_id) VALUES +('Arnold S.', 1), +('Silvester S.', 2), +('Willis B.', 3); + +DROP TABLE IF EXISTS simple_items CASCADE; +CREATE TABLE simple_items (id bigserial PRIMARY KEY, title VARCHAR(255), price int); +INSERT INTO simple_items (title, price) VALUES +('box', 10), +('sphere', 20), +('maul', 100), +('door', 50), +('camera', 500); + +DROP TABLE IF EXISTS books CASCADE; +CREATE TABLE books (id bigserial PRIMARY KEY, title VARCHAR(255)); +INSERT INTO books (title) VALUES +('Mistborn'), +('Neverwhere'), +('Ambers Chronicles'), +('Harry Potter'), +('Lockwood & Co.'), +('Foundation Trilogy'), +('Liveship Traders Trilogy'), +('A Night in the Lonesome October'), +('Da Vinci Code'), +('Lord of the Ring'); + +DROP TABLE IF EXISTS readers CASCADE; +CREATE TABLE readers (id bigserial PRIMARY KEY, name VARCHAR(255)); +INSERT INTO readers (name) VALUES +('Alexander'), +('Bob'); + +DROP TABLE IF EXISTS books_readers CASCADE; +CREATE TABLE books_readers (book_id bigint, reader_id bigint, FOREIGN KEY (book_id) REFERENCES books (id), FOREIGN KEY (reader_id) REFERENCES readers (id)); +INSERT INTO books_readers (book_id, reader_id) VALUES +(1, 1), +(2, 1), +(3, 1), +(4, 1), +(5, 1), +(6, 1), +(7, 1), +(8, 1), +(9, 1), +(10, 1), +(1, 2), +(2, 2); + +DROP TABLE IF EXISTS universities CASCADE; +CREATE TABLE universities (id bigserial PRIMARY KEY, title VARCHAR(255)); +INSERT INTO universities (title) VALUES +('DSTU'), +('NPU'); + +DROP TABLE IF EXISTS students CASCADE; +CREATE TABLE students (id bigserial PRIMARY KEY, name VARCHAR(255), university_id bigint REFERENCES universities (id)); +INSERT INTO students (name, university_id) VALUES +('Alexander', 1), +('Bob', 2), +('John', 1); + +DROP TABLE IF EXISTS validation_beans CASCADE; +CREATE TABLE validation_beans (id bigserial PRIMARY KEY, email VARCHAR(255), priority int DEFAULT 5, postal_code varchar(6), created_at timestamp, updated_at timestamp); +INSERT INTO validation_beans (email, priority, postal_code) VALUES +('data@mail.ru', 5, '100000'); + +DROP TABLE IF EXISTS items CASCADE; +CREATE TABLE items (id bigserial PRIMARY KEY, title VARCHAR(255), price numeric(6, 2)); +INSERT INTO items (title, price) VALUES +('milk', 79.90), +('bread', 24.90), +('butter', 220.00), +('cheese', 350.55), +('coca-cola', 69.90); + +DROP TABLE IF EXISTS passports CASCADE; +CREATE TABLE passports (pserial int, pnumber int, registration_address VARCHAR(255), PRIMARY KEY (pserial, pnumber)); +INSERT INTO passports (pserial, pnumber, registration_address) VALUES +(6080, 965400, 'country.state.city.street'); + +DROP TABLE IF EXISTS citizens CASCADE; +CREATE TABLE citizens (id bigserial, name varchar(255), passport_serial int, passport_number int, CONSTRAINT fk_passport FOREIGN KEY (passport_serial, passport_number) REFERENCES passports (pserial, pnumber)); +INSERT INTO citizens (name, passport_serial, passport_number) VALUES +('Bobby', 6080, 965400); + +DROP TABLE IF EXISTS locking_items CASCADE; +CREATE TABLE locking_items (id bigserial PRIMARY KEY, value int, version int); + +DROP TABLE IF EXISTS alive_beans CASCADE; +CREATE TABLE alive_beans (id bigserial PRIMARY KEY, name varchar(255)); + +DROP TABLE IF EXISTS bottomless_boxes CASCADE; +CREATE TABLE bottomless_boxes (id bigserial PRIMARY KEY, title varchar(255)); +INSERT INTO bottomless_boxes (title) VALUES +('Big Green Box'), +('Red Box'); + +DROP TABLE IF EXISTS things CASCADE; +CREATE TABLE things (id bigserial, title varchar(255), box_id bigint REFERENCES bottomless_boxes (id)); +INSERT INTO things (title, box_id) VALUES +('toy', 1), +('ball', 1), +('pen', 2), +('pencil', null); + +DROP TABLE IF EXISTS simple_entities CASCADE; +CREATE TABLE simple_entities (id bigserial PRIMARY KEY, name varchar(255)); +INSERT INTO simple_entities (name) VALUES ('item 1'); + +DROP TABLE IF EXISTS orders CASCADE; +CREATE TABLE orders (id bigserial PRIMARY KEY, title varchar(255)); +INSERT INTO orders (title) VALUES ('order 1'); + +DROP TABLE IF EXISTS products CASCADE; +CREATE TABLE products (id bigserial PRIMARY KEY, title varchar(255), price int, order_id bigint references orders(id)); +INSERT INTO products (title, price, order_id) VALUES ('product 1', 100, 1); + +DROP TABLE IF EXISTS products_test CASCADE; +CREATE TABLE products_test (id bigserial PRIMARY KEY, title varchar(255), price int); +INSERT INTO products_test (title, price) VALUES ('product 1', 100),('product 2', 200),('product 3', 300); + +COMMIT; \ No newline at end of file diff --git a/hibernate-app.iml b/hibernate-app.iml new file mode 100644 index 0000000..81591b7 --- /dev/null +++ b/hibernate-app.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hibernate.html b/hibernate.html new file mode 100644 index 0000000..59f1544 --- /dev/null +++ b/hibernate.html @@ -0,0 +1,161 @@ + + + + + + + +
+

Hibernate

+ +

Сущности (Entities)

+ +

An entity is a lightweight persistent domain object. The primary programming artifact is the entity class. An + entity class may make use of auxiliary classes that serve as helper classes or that are used to represent the + state of the entity.

+ +

Entity Class

+ + + +

Entity State in Persistence Context

+ + + +

Entity Lifecycle

+ + +
+ + \ No newline at end of file diff --git a/lifecycle.jpg b/lifecycle.jpg new file mode 100644 index 0000000000000000000000000000000000000000..38c46b1b9849b2720de0d0f1e1b57437e294c2bd GIT binary patch literal 123897 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%0A3nTWF`rsR{ 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 SBL#9)U}Y;)G6(l{Rl{>%_W##c|KB6uiPueG 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%0ALN}agH)4EW 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 + + 4.0.0 + + com.flamexander + hibernate-app + 1.0-SNAPSHOT + + + 11 + 11 + + + + + org.hibernate + hibernate-core + 5.4.4.Final + + + org.postgresql + postgresql + 42.2.5 + + + + org.hibernate + hibernate-validator + 5.4.3.Final + + + javax.el + javax.el-api + 2.2.4 + + + org.glassfish.web + javax.el + 2.2.4 + + + \ No newline at end of file diff --git a/src/main/java/com/flamexander/hibernate/PrepareDataApp.java b/src/main/java/com/flamexander/hibernate/PrepareDataApp.java new file mode 100644 index 0000000..eff05ad --- /dev/null +++ b/src/main/java/com/flamexander/hibernate/PrepareDataApp.java @@ -0,0 +1,37 @@ +package com.flamexander.hibernate; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.cfg.Configuration; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.stream.Collectors; + +public class PrepareDataApp { + public static void forcePrepareData() { + SessionFactory factory = new Configuration() + .configure("configs/hibernate.cfg.xml") + .buildSessionFactory(); + Session session = null; + try { + String sql = Files.lines(Paths.get("full.sql")).collect(Collectors.joining(" ")); + session = factory.getCurrentSession(); + session.beginTransaction(); + session.createNativeQuery(sql).executeUpdate(); + session.getTransaction().commit(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + factory.close(); + if (session != null) { + session.close(); + } + } + } + + public static void main(String[] args) throws Exception { + forcePrepareData(); + } +} diff --git a/src/main/java/com/flamexander/hibernate/crud/Product.java b/src/main/java/com/flamexander/hibernate/crud/Product.java new file mode 100644 index 0000000..848af16 --- /dev/null +++ b/src/main/java/com/flamexander/hibernate/crud/Product.java @@ -0,0 +1,61 @@ +package com.flamexander.hibernate.crud; + +import javax.persistence.*; + +@Entity +@Table(name = "products_test") +public class Product { + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "title") + private String title; + + @Column(name = "price") + private int price; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public int getPrice() { + return price; + } + + public void setPrice(int price) { + this.price = price; + } + + public Product() { + } + + @Override + public String toString() { + return String.format("SimpleItem [id = %d, title = %s, price = %d]", id, title, price); + } + + public Product(String title, int price) { + this.title = title; + this.price = price; + } + + public Product(Long id, String title, int price) { + this.id = id; + this.title = title; + this.price = price; + } +} diff --git a/src/main/java/com/flamexander/hibernate/crud/ProductDao.java b/src/main/java/com/flamexander/hibernate/crud/ProductDao.java new file mode 100644 index 0000000..c5ff153 --- /dev/null +++ b/src/main/java/com/flamexander/hibernate/crud/ProductDao.java @@ -0,0 +1,107 @@ +package com.flamexander.hibernate.crud; + +import com.flamexander.hibernate.PrepareDataApp; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.cfg.Configuration; + +import javax.sound.midi.Soundbank; +import java.util.List; + +public class ProductDao { + private static SessionFactory factory; + + public static void main(String[] args) { + try { + init(); + //findById(3L); + //deleteById(2L); + //findAll(); + saveOrUpdate(new Product(1L,"Moloko+",300)); + //saveOrUpdate(new Product("Bread",100)); + } catch (Exception e) { + e.printStackTrace(); + } finally { + shutdown(); + } + } + + private static void init() { + PrepareDataApp.forcePrepareData(); + factory = new Configuration() + .configure("configs/crud/hibernate.cfg.xml") + .buildSessionFactory(); + } + + private static void shutdown() { + factory.close(); + } + +// Создайте класс ProductDao и реализуйте в нем логику выполнения CRUD-операций +// над сущностью Product +// + (Product findById(Long id), +// + List findAll(), +// + void deleteById(Long id), +// - Product saveOrUpdate(Product product)) + + private static Product findById(Long id) { + try (Session session = factory.getCurrentSession()) { + session.beginTransaction(); + Product product = session.get(Product.class, id); + session.getTransaction().commit(); + return product; + } + } + + public static List findAll() { + try (Session session = factory.getCurrentSession()) { + session.beginTransaction(); + List items = session.createQuery("from Product ").getResultList(); + session.getTransaction().commit(); + return items; + } + } + + private static void deleteById(Long id) { + try (Session session = factory.getCurrentSession()) { + session.beginTransaction(); + Product product = session.get(Product.class, id); + session.delete(product); + session.getTransaction().commit(); + } + } + + public static Product saveOrUpdate(Product p) { + try (Session session = factory.getCurrentSession()) { + session.beginTransaction(); + List items = session.createQuery("from Product ").getResultList(); + + +// SimpleItem simpleItem = session.get(SimpleItem.class, 1L); +// simpleItem.setPrice(10000); +// //simpleItem.setPrice(10); +// session.getTransaction().commit(); + + + + if (p.getId() != null) { // Update + for (int i = 0; i < items.size(); i++) { + if (items.get(i).getId() == p.getId()) { + + Product product = session.get(Product.class,p.getId()); + System.out.println(product); + System.out.println(p);// молоко + product.setPrice(p.getPrice()); + product.setTitle(p.getTitle()); + + //session.save(p); + session.getTransaction().commit(); + return p; + } + } + } // Save + session.save(p); + return p; + } + } +} diff --git a/src/main/java/com/flamexander/hibernate/crud/RollbackApp.java b/src/main/java/com/flamexander/hibernate/crud/RollbackApp.java new file mode 100644 index 0000000..f5242dc --- /dev/null +++ b/src/main/java/com/flamexander/hibernate/crud/RollbackApp.java @@ -0,0 +1,35 @@ +package com.flamexander.hibernate.crud; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.cfg.Configuration; + +public class RollbackApp { + public static void main(String[] args) { + SessionFactory factory = new Configuration() + .configure("configs/crud/hibernate.cfg.xml") + .buildSessionFactory(); + + Session session = null; + try { + session = factory.getCurrentSession(); + session.beginTransaction(); + SimpleItem newSimpleItem = new SimpleItem("Chair", 1000); + System.out.println("Before save: " + newSimpleItem); + session.save(newSimpleItem); + System.out.println("After save: " + newSimpleItem); + session.getTransaction().rollback(); + System.out.println("After rollback: " + newSimpleItem); + + session = factory.getCurrentSession(); + session.beginTransaction(); +// System.out.println(session.get(SimpleItem.class, newSimpleItem.getId())); + session.getTransaction().commit(); + } finally { + factory.close(); + if (session != null) { + session.close(); + } + } + } +} diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml new file mode 100644 index 0000000..a9f95f6 --- /dev/null +++ b/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,19 @@ + + + + + JPA Demo + org.hibernate.ejb.HibernatePersistence + + + + + + + + ps + + \ No newline at end of file diff --git a/src/main/resources/configs/cascade/hibernate.cfg.xml b/src/main/resources/configs/cascade/hibernate.cfg.xml new file mode 100644 index 0000000..8c83080 --- /dev/null +++ b/src/main/resources/configs/cascade/hibernate.cfg.xml @@ -0,0 +1,19 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + true + thread + + + + + \ No newline at end of file diff --git a/src/main/resources/configs/composed_key/hibernate.cfg.xml b/src/main/resources/configs/composed_key/hibernate.cfg.xml new file mode 100644 index 0000000..da52201 --- /dev/null +++ b/src/main/resources/configs/composed_key/hibernate.cfg.xml @@ -0,0 +1,19 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + true + thread + + + + + \ No newline at end of file diff --git a/src/main/resources/configs/criteria_api/hibernate.cfg.xml b/src/main/resources/configs/criteria_api/hibernate.cfg.xml new file mode 100644 index 0000000..313cce5 --- /dev/null +++ b/src/main/resources/configs/criteria_api/hibernate.cfg.xml @@ -0,0 +1,18 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + true + thread + + + + \ No newline at end of file diff --git a/src/main/resources/configs/crud/hibernate.cfg.xml b/src/main/resources/configs/crud/hibernate.cfg.xml new file mode 100644 index 0000000..38c4df2 --- /dev/null +++ b/src/main/resources/configs/crud/hibernate.cfg.xml @@ -0,0 +1,19 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + true + thread + + + + + \ No newline at end of file diff --git a/src/main/resources/configs/hibernate.cfg.xml b/src/main/resources/configs/hibernate.cfg.xml new file mode 100644 index 0000000..842887d --- /dev/null +++ b/src/main/resources/configs/hibernate.cfg.xml @@ -0,0 +1,16 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + true + thread + + \ No newline at end of file diff --git a/src/main/resources/configs/inheritance/table_per_class/hibernate.cfg.xml b/src/main/resources/configs/inheritance/table_per_class/hibernate.cfg.xml new file mode 100644 index 0000000..b292c77 --- /dev/null +++ b/src/main/resources/configs/inheritance/table_per_class/hibernate.cfg.xml @@ -0,0 +1,20 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + true + thread + + + + + + \ No newline at end of file diff --git a/src/main/resources/configs/lifecycle/hibernate.cfg.xml b/src/main/resources/configs/lifecycle/hibernate.cfg.xml new file mode 100644 index 0000000..825965a --- /dev/null +++ b/src/main/resources/configs/lifecycle/hibernate.cfg.xml @@ -0,0 +1,18 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + false + thread + + + + \ No newline at end of file diff --git a/src/main/resources/configs/linked_objects/hibernate.cfg.xml b/src/main/resources/configs/linked_objects/hibernate.cfg.xml new file mode 100644 index 0000000..170d4ba --- /dev/null +++ b/src/main/resources/configs/linked_objects/hibernate.cfg.xml @@ -0,0 +1,19 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + false + thread + + + + + \ No newline at end of file diff --git a/src/main/resources/configs/locking/hibernate.cfg.xml b/src/main/resources/configs/locking/hibernate.cfg.xml new file mode 100644 index 0000000..e768598 --- /dev/null +++ b/src/main/resources/configs/locking/hibernate.cfg.xml @@ -0,0 +1,18 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + false + thread + + + + \ No newline at end of file diff --git a/src/main/resources/configs/many_to_many/hibernate.cfg.xml b/src/main/resources/configs/many_to_many/hibernate.cfg.xml new file mode 100644 index 0000000..e82b162 --- /dev/null +++ b/src/main/resources/configs/many_to_many/hibernate.cfg.xml @@ -0,0 +1,19 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + false + thread + + + + + \ No newline at end of file diff --git a/src/main/resources/configs/one_to_many_and_back/hibernate.cfg.xml b/src/main/resources/configs/one_to_many_and_back/hibernate.cfg.xml new file mode 100644 index 0000000..18d01f0 --- /dev/null +++ b/src/main/resources/configs/one_to_many_and_back/hibernate.cfg.xml @@ -0,0 +1,19 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + false + thread + + + + + \ No newline at end of file diff --git a/src/main/resources/configs/one_to_one/hibernate.cfg.xml b/src/main/resources/configs/one_to_one/hibernate.cfg.xml new file mode 100644 index 0000000..50de1a4 --- /dev/null +++ b/src/main/resources/configs/one_to_one/hibernate.cfg.xml @@ -0,0 +1,19 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + true + thread + + + + + \ No newline at end of file diff --git a/src/main/resources/configs/validation/hibernate.cfg.xml b/src/main/resources/configs/validation/hibernate.cfg.xml new file mode 100644 index 0000000..e95b267 --- /dev/null +++ b/src/main/resources/configs/validation/hibernate.cfg.xml @@ -0,0 +1,18 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + false + thread + + + + \ No newline at end of file diff --git a/src/main/resources/hibernate.properties b/src/main/resources/hibernate.properties new file mode 100644 index 0000000..97f7649 --- /dev/null +++ b/src/main/resources/hibernate.properties @@ -0,0 +1,7 @@ +hibernate.connection.driver_class = org.postgresql.Driver +hibernate.connection.url = jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber +hibernate.connection.username = postgres +hibernate.connection.password = admin +hibernate.show_sql=true +hibernate.dialect = org.hibernate.dialect.PostgreSQL95Dialect +hibernate.current_session_context_class=thread \ No newline at end of file diff --git a/target/classes/META-INF/persistence.xml b/target/classes/META-INF/persistence.xml new file mode 100644 index 0000000..a9f95f6 --- /dev/null +++ b/target/classes/META-INF/persistence.xml @@ -0,0 +1,19 @@ + + + + + JPA Demo + org.hibernate.ejb.HibernatePersistence + + + + + + + + ps + + \ No newline at end of file diff --git a/target/classes/configs/cascade/hibernate.cfg.xml b/target/classes/configs/cascade/hibernate.cfg.xml new file mode 100644 index 0000000..8c83080 --- /dev/null +++ b/target/classes/configs/cascade/hibernate.cfg.xml @@ -0,0 +1,19 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + true + thread + + + + + \ No newline at end of file diff --git a/target/classes/configs/composed_key/hibernate.cfg.xml b/target/classes/configs/composed_key/hibernate.cfg.xml new file mode 100644 index 0000000..da52201 --- /dev/null +++ b/target/classes/configs/composed_key/hibernate.cfg.xml @@ -0,0 +1,19 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + true + thread + + + + + \ No newline at end of file diff --git a/target/classes/configs/criteria_api/hibernate.cfg.xml b/target/classes/configs/criteria_api/hibernate.cfg.xml new file mode 100644 index 0000000..313cce5 --- /dev/null +++ b/target/classes/configs/criteria_api/hibernate.cfg.xml @@ -0,0 +1,18 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + true + thread + + + + \ No newline at end of file diff --git a/target/classes/configs/crud/hibernate.cfg.xml b/target/classes/configs/crud/hibernate.cfg.xml new file mode 100644 index 0000000..38c4df2 --- /dev/null +++ b/target/classes/configs/crud/hibernate.cfg.xml @@ -0,0 +1,19 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + true + thread + + + + + \ No newline at end of file diff --git a/target/classes/configs/hibernate.cfg.xml b/target/classes/configs/hibernate.cfg.xml new file mode 100644 index 0000000..842887d --- /dev/null +++ b/target/classes/configs/hibernate.cfg.xml @@ -0,0 +1,16 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + true + thread + + \ No newline at end of file diff --git a/target/classes/configs/inheritance/table_per_class/hibernate.cfg.xml b/target/classes/configs/inheritance/table_per_class/hibernate.cfg.xml new file mode 100644 index 0000000..b292c77 --- /dev/null +++ b/target/classes/configs/inheritance/table_per_class/hibernate.cfg.xml @@ -0,0 +1,20 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + true + thread + + + + + + \ No newline at end of file diff --git a/target/classes/configs/lifecycle/hibernate.cfg.xml b/target/classes/configs/lifecycle/hibernate.cfg.xml new file mode 100644 index 0000000..825965a --- /dev/null +++ b/target/classes/configs/lifecycle/hibernate.cfg.xml @@ -0,0 +1,18 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + false + thread + + + + \ No newline at end of file diff --git a/target/classes/configs/linked_objects/hibernate.cfg.xml b/target/classes/configs/linked_objects/hibernate.cfg.xml new file mode 100644 index 0000000..170d4ba --- /dev/null +++ b/target/classes/configs/linked_objects/hibernate.cfg.xml @@ -0,0 +1,19 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + false + thread + + + + + \ No newline at end of file diff --git a/target/classes/configs/locking/hibernate.cfg.xml b/target/classes/configs/locking/hibernate.cfg.xml new file mode 100644 index 0000000..e768598 --- /dev/null +++ b/target/classes/configs/locking/hibernate.cfg.xml @@ -0,0 +1,18 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + false + thread + + + + \ No newline at end of file diff --git a/target/classes/configs/many_to_many/hibernate.cfg.xml b/target/classes/configs/many_to_many/hibernate.cfg.xml new file mode 100644 index 0000000..e82b162 --- /dev/null +++ b/target/classes/configs/many_to_many/hibernate.cfg.xml @@ -0,0 +1,19 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + false + thread + + + + + \ No newline at end of file diff --git a/target/classes/configs/one_to_many_and_back/hibernate.cfg.xml b/target/classes/configs/one_to_many_and_back/hibernate.cfg.xml new file mode 100644 index 0000000..18d01f0 --- /dev/null +++ b/target/classes/configs/one_to_many_and_back/hibernate.cfg.xml @@ -0,0 +1,19 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + false + thread + + + + + \ No newline at end of file diff --git a/target/classes/configs/one_to_one/hibernate.cfg.xml b/target/classes/configs/one_to_one/hibernate.cfg.xml new file mode 100644 index 0000000..50de1a4 --- /dev/null +++ b/target/classes/configs/one_to_one/hibernate.cfg.xml @@ -0,0 +1,19 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + true + thread + + + + + \ No newline at end of file diff --git a/target/classes/configs/validation/hibernate.cfg.xml b/target/classes/configs/validation/hibernate.cfg.xml new file mode 100644 index 0000000..e95b267 --- /dev/null +++ b/target/classes/configs/validation/hibernate.cfg.xml @@ -0,0 +1,18 @@ + + + + + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber + postgres + admin + 8 + org.hibernate.dialect.PostgreSQL94Dialect + false + thread + + + + \ No newline at end of file diff --git a/target/classes/hibernate.properties b/target/classes/hibernate.properties new file mode 100644 index 0000000..97f7649 --- /dev/null +++ b/target/classes/hibernate.properties @@ -0,0 +1,7 @@ +hibernate.connection.driver_class = org.postgresql.Driver +hibernate.connection.url = jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber +hibernate.connection.username = postgres +hibernate.connection.password = admin +hibernate.show_sql=true +hibernate.dialect = org.hibernate.dialect.PostgreSQL95Dialect +hibernate.current_session_context_class=thread \ No newline at end of file From 250a19d2f608b556121b4277d9c5e15891fa5bb6 Mon Sep 17 00:00:00 2001 From: Vyaceslav Sv Date: Mon, 25 Jan 2021 09:57:50 +0300 Subject: [PATCH 2/6] lesson07 --- mvnw | 310 ++++++++++++++++++ mvnw.cmd | 182 ++++++++++ pom.xml | 107 +++--- .../flamexander/hibernate/PrepareDataApp.java | 37 --- .../flamexander/hibernate/crud/Product.java | 61 ---- .../hibernate/crud/ProductDao.java | 107 ------ .../hibernate/crud/RollbackApp.java | 35 -- .../spring/Lesson07Application.java | 13 + .../spring/controllers/ProductController.java | 106 ++++++ .../ru/geekbrains/spring/model/Product.java | 25 ++ .../reposisitory/ProductRepository.java | 19 ++ src/main/resources/META-INF/persistence.xml | 19 -- src/main/resources/application.yaml | 20 ++ .../configs/cascade/hibernate.cfg.xml | 19 -- .../configs/composed_key/hibernate.cfg.xml | 19 -- .../configs/criteria_api/hibernate.cfg.xml | 18 - .../resources/configs/crud/hibernate.cfg.xml | 19 -- src/main/resources/configs/hibernate.cfg.xml | 16 - .../table_per_class/hibernate.cfg.xml | 20 -- .../configs/lifecycle/hibernate.cfg.xml | 18 - .../configs/linked_objects/hibernate.cfg.xml | 19 -- .../configs/locking/hibernate.cfg.xml | 18 - .../configs/many_to_many/hibernate.cfg.xml | 19 -- .../one_to_many_and_back/hibernate.cfg.xml | 19 -- .../configs/one_to_one/hibernate.cfg.xml | 19 -- .../configs/validation/hibernate.cfg.xml | 18 - .../resources/db/migration/V1__initialize.sql | 22 ++ src/main/resources/hibernate.properties | 7 - .../spring/Lesson07ApplicationTests.java | 13 + target/classes/META-INF/persistence.xml | 19 -- .../classes/configs/cascade/hibernate.cfg.xml | 19 -- .../configs/composed_key/hibernate.cfg.xml | 19 -- .../configs/criteria_api/hibernate.cfg.xml | 18 - target/classes/configs/crud/hibernate.cfg.xml | 19 -- target/classes/configs/hibernate.cfg.xml | 16 - .../table_per_class/hibernate.cfg.xml | 20 -- .../configs/lifecycle/hibernate.cfg.xml | 18 - .../configs/linked_objects/hibernate.cfg.xml | 19 -- .../classes/configs/locking/hibernate.cfg.xml | 18 - .../configs/many_to_many/hibernate.cfg.xml | 19 -- .../one_to_many_and_back/hibernate.cfg.xml | 19 -- .../configs/one_to_one/hibernate.cfg.xml | 19 -- .../configs/validation/hibernate.cfg.xml | 18 - target/classes/hibernate.properties | 7 - 44 files changed, 777 insertions(+), 814 deletions(-) create mode 100644 mvnw create mode 100644 mvnw.cmd delete mode 100644 src/main/java/com/flamexander/hibernate/PrepareDataApp.java delete mode 100644 src/main/java/com/flamexander/hibernate/crud/Product.java delete mode 100644 src/main/java/com/flamexander/hibernate/crud/ProductDao.java delete mode 100644 src/main/java/com/flamexander/hibernate/crud/RollbackApp.java create mode 100644 src/main/java/ru/geekbrains/spring/Lesson07Application.java create mode 100644 src/main/java/ru/geekbrains/spring/controllers/ProductController.java create mode 100644 src/main/java/ru/geekbrains/spring/model/Product.java create mode 100644 src/main/java/ru/geekbrains/spring/reposisitory/ProductRepository.java delete mode 100644 src/main/resources/META-INF/persistence.xml create mode 100644 src/main/resources/application.yaml delete mode 100644 src/main/resources/configs/cascade/hibernate.cfg.xml delete mode 100644 src/main/resources/configs/composed_key/hibernate.cfg.xml delete mode 100644 src/main/resources/configs/criteria_api/hibernate.cfg.xml delete mode 100644 src/main/resources/configs/crud/hibernate.cfg.xml delete mode 100644 src/main/resources/configs/hibernate.cfg.xml delete mode 100644 src/main/resources/configs/inheritance/table_per_class/hibernate.cfg.xml delete mode 100644 src/main/resources/configs/lifecycle/hibernate.cfg.xml delete mode 100644 src/main/resources/configs/linked_objects/hibernate.cfg.xml delete mode 100644 src/main/resources/configs/locking/hibernate.cfg.xml delete mode 100644 src/main/resources/configs/many_to_many/hibernate.cfg.xml delete mode 100644 src/main/resources/configs/one_to_many_and_back/hibernate.cfg.xml delete mode 100644 src/main/resources/configs/one_to_one/hibernate.cfg.xml delete mode 100644 src/main/resources/configs/validation/hibernate.cfg.xml create mode 100644 src/main/resources/db/migration/V1__initialize.sql delete mode 100644 src/main/resources/hibernate.properties create mode 100644 src/test/java/ru/geekbrains/spring/Lesson07ApplicationTests.java delete mode 100644 target/classes/META-INF/persistence.xml delete mode 100644 target/classes/configs/cascade/hibernate.cfg.xml delete mode 100644 target/classes/configs/composed_key/hibernate.cfg.xml delete mode 100644 target/classes/configs/criteria_api/hibernate.cfg.xml delete mode 100644 target/classes/configs/crud/hibernate.cfg.xml delete mode 100644 target/classes/configs/hibernate.cfg.xml delete mode 100644 target/classes/configs/inheritance/table_per_class/hibernate.cfg.xml delete mode 100644 target/classes/configs/lifecycle/hibernate.cfg.xml delete mode 100644 target/classes/configs/linked_objects/hibernate.cfg.xml delete mode 100644 target/classes/configs/locking/hibernate.cfg.xml delete mode 100644 target/classes/configs/many_to_many/hibernate.cfg.xml delete mode 100644 target/classes/configs/one_to_many_and_back/hibernate.cfg.xml delete mode 100644 target/classes/configs/one_to_one/hibernate.cfg.xml delete mode 100644 target/classes/configs/validation/hibernate.cfg.xml delete mode 100644 target/classes/hibernate.properties diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..a16b543 --- /dev/null +++ b/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index f4d96da..8fbf3c1 100644 --- a/pom.xml +++ b/pom.xml @@ -1,44 +1,71 @@ - - 4.0.0 + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.2 + + + ru.geekbrains.spring.lesson07 + lesson07 + 0.0.1-SNAPSHOT + lesson07 + Demo project for Spring Boot + + 11 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-web + + + org.flywaydb + flyway-core + - com.flamexander - hibernate-app - 1.0-SNAPSHOT + + com.h2database + h2 + runtime + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + - - 11 - 11 - + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + - - - org.hibernate - hibernate-core - 5.4.4.Final - - - org.postgresql - postgresql - 42.2.5 - - - - org.hibernate - hibernate-validator - 5.4.3.Final - - - javax.el - javax.el-api - 2.2.4 - - - org.glassfish.web - javax.el - 2.2.4 - - - \ No newline at end of file + diff --git a/src/main/java/com/flamexander/hibernate/PrepareDataApp.java b/src/main/java/com/flamexander/hibernate/PrepareDataApp.java deleted file mode 100644 index eff05ad..0000000 --- a/src/main/java/com/flamexander/hibernate/PrepareDataApp.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.flamexander.hibernate; - -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.hibernate.cfg.Configuration; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.stream.Collectors; - -public class PrepareDataApp { - public static void forcePrepareData() { - SessionFactory factory = new Configuration() - .configure("configs/hibernate.cfg.xml") - .buildSessionFactory(); - Session session = null; - try { - String sql = Files.lines(Paths.get("full.sql")).collect(Collectors.joining(" ")); - session = factory.getCurrentSession(); - session.beginTransaction(); - session.createNativeQuery(sql).executeUpdate(); - session.getTransaction().commit(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - factory.close(); - if (session != null) { - session.close(); - } - } - } - - public static void main(String[] args) throws Exception { - forcePrepareData(); - } -} diff --git a/src/main/java/com/flamexander/hibernate/crud/Product.java b/src/main/java/com/flamexander/hibernate/crud/Product.java deleted file mode 100644 index 848af16..0000000 --- a/src/main/java/com/flamexander/hibernate/crud/Product.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.flamexander.hibernate.crud; - -import javax.persistence.*; - -@Entity -@Table(name = "products_test") -public class Product { - @Id - @Column(name = "id") - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "title") - private String title; - - @Column(name = "price") - private int price; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public int getPrice() { - return price; - } - - public void setPrice(int price) { - this.price = price; - } - - public Product() { - } - - @Override - public String toString() { - return String.format("SimpleItem [id = %d, title = %s, price = %d]", id, title, price); - } - - public Product(String title, int price) { - this.title = title; - this.price = price; - } - - public Product(Long id, String title, int price) { - this.id = id; - this.title = title; - this.price = price; - } -} diff --git a/src/main/java/com/flamexander/hibernate/crud/ProductDao.java b/src/main/java/com/flamexander/hibernate/crud/ProductDao.java deleted file mode 100644 index c5ff153..0000000 --- a/src/main/java/com/flamexander/hibernate/crud/ProductDao.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.flamexander.hibernate.crud; - -import com.flamexander.hibernate.PrepareDataApp; -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.hibernate.cfg.Configuration; - -import javax.sound.midi.Soundbank; -import java.util.List; - -public class ProductDao { - private static SessionFactory factory; - - public static void main(String[] args) { - try { - init(); - //findById(3L); - //deleteById(2L); - //findAll(); - saveOrUpdate(new Product(1L,"Moloko+",300)); - //saveOrUpdate(new Product("Bread",100)); - } catch (Exception e) { - e.printStackTrace(); - } finally { - shutdown(); - } - } - - private static void init() { - PrepareDataApp.forcePrepareData(); - factory = new Configuration() - .configure("configs/crud/hibernate.cfg.xml") - .buildSessionFactory(); - } - - private static void shutdown() { - factory.close(); - } - -// Создайте класс ProductDao и реализуйте в нем логику выполнения CRUD-операций -// над сущностью Product -// + (Product findById(Long id), -// + List findAll(), -// + void deleteById(Long id), -// - Product saveOrUpdate(Product product)) - - private static Product findById(Long id) { - try (Session session = factory.getCurrentSession()) { - session.beginTransaction(); - Product product = session.get(Product.class, id); - session.getTransaction().commit(); - return product; - } - } - - public static List findAll() { - try (Session session = factory.getCurrentSession()) { - session.beginTransaction(); - List items = session.createQuery("from Product ").getResultList(); - session.getTransaction().commit(); - return items; - } - } - - private static void deleteById(Long id) { - try (Session session = factory.getCurrentSession()) { - session.beginTransaction(); - Product product = session.get(Product.class, id); - session.delete(product); - session.getTransaction().commit(); - } - } - - public static Product saveOrUpdate(Product p) { - try (Session session = factory.getCurrentSession()) { - session.beginTransaction(); - List items = session.createQuery("from Product ").getResultList(); - - -// SimpleItem simpleItem = session.get(SimpleItem.class, 1L); -// simpleItem.setPrice(10000); -// //simpleItem.setPrice(10); -// session.getTransaction().commit(); - - - - if (p.getId() != null) { // Update - for (int i = 0; i < items.size(); i++) { - if (items.get(i).getId() == p.getId()) { - - Product product = session.get(Product.class,p.getId()); - System.out.println(product); - System.out.println(p);// молоко - product.setPrice(p.getPrice()); - product.setTitle(p.getTitle()); - - //session.save(p); - session.getTransaction().commit(); - return p; - } - } - } // Save - session.save(p); - return p; - } - } -} diff --git a/src/main/java/com/flamexander/hibernate/crud/RollbackApp.java b/src/main/java/com/flamexander/hibernate/crud/RollbackApp.java deleted file mode 100644 index f5242dc..0000000 --- a/src/main/java/com/flamexander/hibernate/crud/RollbackApp.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.flamexander.hibernate.crud; - -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.hibernate.cfg.Configuration; - -public class RollbackApp { - public static void main(String[] args) { - SessionFactory factory = new Configuration() - .configure("configs/crud/hibernate.cfg.xml") - .buildSessionFactory(); - - Session session = null; - try { - session = factory.getCurrentSession(); - session.beginTransaction(); - SimpleItem newSimpleItem = new SimpleItem("Chair", 1000); - System.out.println("Before save: " + newSimpleItem); - session.save(newSimpleItem); - System.out.println("After save: " + newSimpleItem); - session.getTransaction().rollback(); - System.out.println("After rollback: " + newSimpleItem); - - session = factory.getCurrentSession(); - session.beginTransaction(); -// System.out.println(session.get(SimpleItem.class, newSimpleItem.getId())); - session.getTransaction().commit(); - } finally { - factory.close(); - if (session != null) { - session.close(); - } - } - } -} diff --git a/src/main/java/ru/geekbrains/spring/Lesson07Application.java b/src/main/java/ru/geekbrains/spring/Lesson07Application.java new file mode 100644 index 0000000..2b7cb09 --- /dev/null +++ b/src/main/java/ru/geekbrains/spring/Lesson07Application.java @@ -0,0 +1,13 @@ +package ru.geekbrains.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Lesson07Application { + + public static void main(String[] args) { + SpringApplication.run(Lesson07Application.class, args); + } + +} diff --git a/src/main/java/ru/geekbrains/spring/controllers/ProductController.java b/src/main/java/ru/geekbrains/spring/controllers/ProductController.java new file mode 100644 index 0000000..0da4008 --- /dev/null +++ b/src/main/java/ru/geekbrains/spring/controllers/ProductController.java @@ -0,0 +1,106 @@ +package ru.geekbrains.spring.controllers; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import ru.geekbrains.spring.model.Product; +import ru.geekbrains.spring.reposisitory.ProductRepository; + +import java.util.List; + + +@RestController +@RequestMapping("/products") +@RequiredArgsConstructor +public class ProductController { + +// Сделать RestController позволяющий выполнять следующий набор операции над этой сущностью: +// получение товара по id [ GET .../app/products/{id} ] + +// получение всех товаров [ GET .../app/products ] + +// создание нового товара [ POST .../app/products ] + +// удаление товара по id.[ GET .../app/products/delete/{id} ] + +// (Замечание: пока делаем немного не по правилам REST API, эта тема будет разбираться на следующих занятиях, поэтому удаление выполняется через http-метод GET, а не DELETE) + + private final ProductRepository productRepository; + + @GetMapping("/{id}") + public Product getProductById(@PathVariable Long id) { + return productRepository.findById(id).get(); + } + + @GetMapping + public List findAll() { + return productRepository.findAll(); + } + + @GetMapping("delete/{id}") + public List deleteById(@PathVariable Long id) { + productRepository.deleteById(id); + return productRepository.findAll(); + } + + @PostMapping // для создания новых ресурсов + public Product save(@RequestBody Product product) { + //product.setId(null); + return productRepository.save(product); + } + +// * К запросу всех товаров добавьте возможность фильтрации по минимальной и максимальной цене +// (в трех вариантах: +// товары дороже min цены, + +// товары дешевле max цены, + +// или товары, цена которых находится в пределах min-max). + + + // Методы к заданию со * : +// @GetMapping("/test") +// public List test(@RequestParam(name = "min") Integer min) { +// return productRepository.findAllByPriceGreaterThanEqual(min); +// } +// @GetMapping("/test") +// public List test(@RequestParam(name = "min") Integer min,@RequestParam(name = "max") Integer max) { +// return productRepository.findAllByPriceBetween(min,max); +// } +// @GetMapping("/test") +// public List test(@RequestParam(name = "max") Integer max) { +// return productRepository.findAllByPriceLessThanEqual(max); +// } + + +} + + +// +//@RestController +//@RequestMapping("/students") +//public class StudentController { +// +// private final StudentRepository studentRepository; +// +// @GetMapping +// public List getAllStudents(){ +// return (List) studentRepository.findAll(); +// } +// +// @GetMapping("/{id}") +// public Student getStudentById(@PathVariable Long id){ +// return studentRepository.findById(id).get(); +// } +// +// @GetMapping("/search_by_name") +// public Student searchByName(@RequestParam String name) { +// return studentRepository.findByName(name).get(); +// } +// +// @GetMapping("/search_by_min_score") +// public List searchByMinScore(@RequestParam (name = "min_score") Integer minScore) { +// return studentRepository.findAllByScoreIsGreaterThanEqual(minScore); +// } +// @GetMapping("/test") +// public Student testMethod() { +// return studentRepository.customFinderById(2L, "Bob").get(); +// } +// +// public StudentController(StudentRepository studentRepository) { +// this.studentRepository = studentRepository; +// } +// +//} diff --git a/src/main/java/ru/geekbrains/spring/model/Product.java b/src/main/java/ru/geekbrains/spring/model/Product.java new file mode 100644 index 0000000..e3689b4 --- /dev/null +++ b/src/main/java/ru/geekbrains/spring/model/Product.java @@ -0,0 +1,25 @@ +package ru.geekbrains.spring.model; + + +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@Entity +@Table(name = "products") +@Data +@NoArgsConstructor +public class Product { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "title") + private String title; + + @Column(name = "price") + private int price; + +} diff --git a/src/main/java/ru/geekbrains/spring/reposisitory/ProductRepository.java b/src/main/java/ru/geekbrains/spring/reposisitory/ProductRepository.java new file mode 100644 index 0000000..285208e --- /dev/null +++ b/src/main/java/ru/geekbrains/spring/reposisitory/ProductRepository.java @@ -0,0 +1,19 @@ +package ru.geekbrains.spring.reposisitory; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; +import ru.geekbrains.spring.model.Product; + +import java.util.List; + +@Repository +public interface ProductRepository extends JpaRepository { + + List findAllByPriceGreaterThanEqual(int min); + + List findAllByPriceLessThanEqual(int max); + + List findAllByPriceBetween(int min, int max); + +} diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml deleted file mode 100644 index a9f95f6..0000000 --- a/src/main/resources/META-INF/persistence.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - JPA Demo - org.hibernate.ejb.HibernatePersistence - - - - - - - - ps - - \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..90aeb98 --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,20 @@ +server: + port: 8189 + servlet: + context-path: /app +spring: + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:mydatabase;MODE=PostgreSQL + username: sa + password: + jpa: + show-sql: true + properties: + hibernate: + dialect: org.hibernate.dialect.H2Dialect + h2: + console: + enabled: true + settings: + web-allow-others: false diff --git a/src/main/resources/configs/cascade/hibernate.cfg.xml b/src/main/resources/configs/cascade/hibernate.cfg.xml deleted file mode 100644 index 8c83080..0000000 --- a/src/main/resources/configs/cascade/hibernate.cfg.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - true - thread - - - - - \ No newline at end of file diff --git a/src/main/resources/configs/composed_key/hibernate.cfg.xml b/src/main/resources/configs/composed_key/hibernate.cfg.xml deleted file mode 100644 index da52201..0000000 --- a/src/main/resources/configs/composed_key/hibernate.cfg.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - true - thread - - - - - \ No newline at end of file diff --git a/src/main/resources/configs/criteria_api/hibernate.cfg.xml b/src/main/resources/configs/criteria_api/hibernate.cfg.xml deleted file mode 100644 index 313cce5..0000000 --- a/src/main/resources/configs/criteria_api/hibernate.cfg.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - true - thread - - - - \ No newline at end of file diff --git a/src/main/resources/configs/crud/hibernate.cfg.xml b/src/main/resources/configs/crud/hibernate.cfg.xml deleted file mode 100644 index 38c4df2..0000000 --- a/src/main/resources/configs/crud/hibernate.cfg.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - true - thread - - - - - \ No newline at end of file diff --git a/src/main/resources/configs/hibernate.cfg.xml b/src/main/resources/configs/hibernate.cfg.xml deleted file mode 100644 index 842887d..0000000 --- a/src/main/resources/configs/hibernate.cfg.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - true - thread - - \ No newline at end of file diff --git a/src/main/resources/configs/inheritance/table_per_class/hibernate.cfg.xml b/src/main/resources/configs/inheritance/table_per_class/hibernate.cfg.xml deleted file mode 100644 index b292c77..0000000 --- a/src/main/resources/configs/inheritance/table_per_class/hibernate.cfg.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - true - thread - - - - - - \ No newline at end of file diff --git a/src/main/resources/configs/lifecycle/hibernate.cfg.xml b/src/main/resources/configs/lifecycle/hibernate.cfg.xml deleted file mode 100644 index 825965a..0000000 --- a/src/main/resources/configs/lifecycle/hibernate.cfg.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - false - thread - - - - \ No newline at end of file diff --git a/src/main/resources/configs/linked_objects/hibernate.cfg.xml b/src/main/resources/configs/linked_objects/hibernate.cfg.xml deleted file mode 100644 index 170d4ba..0000000 --- a/src/main/resources/configs/linked_objects/hibernate.cfg.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - false - thread - - - - - \ No newline at end of file diff --git a/src/main/resources/configs/locking/hibernate.cfg.xml b/src/main/resources/configs/locking/hibernate.cfg.xml deleted file mode 100644 index e768598..0000000 --- a/src/main/resources/configs/locking/hibernate.cfg.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - false - thread - - - - \ No newline at end of file diff --git a/src/main/resources/configs/many_to_many/hibernate.cfg.xml b/src/main/resources/configs/many_to_many/hibernate.cfg.xml deleted file mode 100644 index e82b162..0000000 --- a/src/main/resources/configs/many_to_many/hibernate.cfg.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - false - thread - - - - - \ No newline at end of file diff --git a/src/main/resources/configs/one_to_many_and_back/hibernate.cfg.xml b/src/main/resources/configs/one_to_many_and_back/hibernate.cfg.xml deleted file mode 100644 index 18d01f0..0000000 --- a/src/main/resources/configs/one_to_many_and_back/hibernate.cfg.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - false - thread - - - - - \ No newline at end of file diff --git a/src/main/resources/configs/one_to_one/hibernate.cfg.xml b/src/main/resources/configs/one_to_one/hibernate.cfg.xml deleted file mode 100644 index 50de1a4..0000000 --- a/src/main/resources/configs/one_to_one/hibernate.cfg.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - true - thread - - - - - \ No newline at end of file diff --git a/src/main/resources/configs/validation/hibernate.cfg.xml b/src/main/resources/configs/validation/hibernate.cfg.xml deleted file mode 100644 index e95b267..0000000 --- a/src/main/resources/configs/validation/hibernate.cfg.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - false - thread - - - - \ No newline at end of file diff --git a/src/main/resources/db/migration/V1__initialize.sql b/src/main/resources/db/migration/V1__initialize.sql new file mode 100644 index 0000000..04d53be --- /dev/null +++ b/src/main/resources/db/migration/V1__initialize.sql @@ -0,0 +1,22 @@ +create table products (id bigserial primary key, title varchar (255), price int); +insert into products (title, price) values +('product_1',100), +('product_2',200), +('product_3',300), +('product_4',400), +('product_5',500), +('product_6',600), +('product_7',700), +('product_8',800), +('product_9',900), +('product_10',1000), +('product_11',1100), +('product_12',1200), +('product_13',1300), +('product_14',1400), +('product_15',1500), +('product_16',1600), +('product_17',1700), +('product_18',1800), +('product_19',1900), +('product_20',2000); diff --git a/src/main/resources/hibernate.properties b/src/main/resources/hibernate.properties deleted file mode 100644 index 97f7649..0000000 --- a/src/main/resources/hibernate.properties +++ /dev/null @@ -1,7 +0,0 @@ -hibernate.connection.driver_class = org.postgresql.Driver -hibernate.connection.url = jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber -hibernate.connection.username = postgres -hibernate.connection.password = admin -hibernate.show_sql=true -hibernate.dialect = org.hibernate.dialect.PostgreSQL95Dialect -hibernate.current_session_context_class=thread \ No newline at end of file diff --git a/src/test/java/ru/geekbrains/spring/Lesson07ApplicationTests.java b/src/test/java/ru/geekbrains/spring/Lesson07ApplicationTests.java new file mode 100644 index 0000000..7342674 --- /dev/null +++ b/src/test/java/ru/geekbrains/spring/Lesson07ApplicationTests.java @@ -0,0 +1,13 @@ +package ru.geekbrains.spring; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Lesson07ApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/target/classes/META-INF/persistence.xml b/target/classes/META-INF/persistence.xml deleted file mode 100644 index a9f95f6..0000000 --- a/target/classes/META-INF/persistence.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - JPA Demo - org.hibernate.ejb.HibernatePersistence - - - - - - - - ps - - \ No newline at end of file diff --git a/target/classes/configs/cascade/hibernate.cfg.xml b/target/classes/configs/cascade/hibernate.cfg.xml deleted file mode 100644 index 8c83080..0000000 --- a/target/classes/configs/cascade/hibernate.cfg.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - true - thread - - - - - \ No newline at end of file diff --git a/target/classes/configs/composed_key/hibernate.cfg.xml b/target/classes/configs/composed_key/hibernate.cfg.xml deleted file mode 100644 index da52201..0000000 --- a/target/classes/configs/composed_key/hibernate.cfg.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - true - thread - - - - - \ No newline at end of file diff --git a/target/classes/configs/criteria_api/hibernate.cfg.xml b/target/classes/configs/criteria_api/hibernate.cfg.xml deleted file mode 100644 index 313cce5..0000000 --- a/target/classes/configs/criteria_api/hibernate.cfg.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - true - thread - - - - \ No newline at end of file diff --git a/target/classes/configs/crud/hibernate.cfg.xml b/target/classes/configs/crud/hibernate.cfg.xml deleted file mode 100644 index 38c4df2..0000000 --- a/target/classes/configs/crud/hibernate.cfg.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - true - thread - - - - - \ No newline at end of file diff --git a/target/classes/configs/hibernate.cfg.xml b/target/classes/configs/hibernate.cfg.xml deleted file mode 100644 index 842887d..0000000 --- a/target/classes/configs/hibernate.cfg.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - true - thread - - \ No newline at end of file diff --git a/target/classes/configs/inheritance/table_per_class/hibernate.cfg.xml b/target/classes/configs/inheritance/table_per_class/hibernate.cfg.xml deleted file mode 100644 index b292c77..0000000 --- a/target/classes/configs/inheritance/table_per_class/hibernate.cfg.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - true - thread - - - - - - \ No newline at end of file diff --git a/target/classes/configs/lifecycle/hibernate.cfg.xml b/target/classes/configs/lifecycle/hibernate.cfg.xml deleted file mode 100644 index 825965a..0000000 --- a/target/classes/configs/lifecycle/hibernate.cfg.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - false - thread - - - - \ No newline at end of file diff --git a/target/classes/configs/linked_objects/hibernate.cfg.xml b/target/classes/configs/linked_objects/hibernate.cfg.xml deleted file mode 100644 index 170d4ba..0000000 --- a/target/classes/configs/linked_objects/hibernate.cfg.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - false - thread - - - - - \ No newline at end of file diff --git a/target/classes/configs/locking/hibernate.cfg.xml b/target/classes/configs/locking/hibernate.cfg.xml deleted file mode 100644 index e768598..0000000 --- a/target/classes/configs/locking/hibernate.cfg.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - false - thread - - - - \ No newline at end of file diff --git a/target/classes/configs/many_to_many/hibernate.cfg.xml b/target/classes/configs/many_to_many/hibernate.cfg.xml deleted file mode 100644 index e82b162..0000000 --- a/target/classes/configs/many_to_many/hibernate.cfg.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - false - thread - - - - - \ No newline at end of file diff --git a/target/classes/configs/one_to_many_and_back/hibernate.cfg.xml b/target/classes/configs/one_to_many_and_back/hibernate.cfg.xml deleted file mode 100644 index 18d01f0..0000000 --- a/target/classes/configs/one_to_many_and_back/hibernate.cfg.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - false - thread - - - - - \ No newline at end of file diff --git a/target/classes/configs/one_to_one/hibernate.cfg.xml b/target/classes/configs/one_to_one/hibernate.cfg.xml deleted file mode 100644 index 50de1a4..0000000 --- a/target/classes/configs/one_to_one/hibernate.cfg.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - true - thread - - - - - \ No newline at end of file diff --git a/target/classes/configs/validation/hibernate.cfg.xml b/target/classes/configs/validation/hibernate.cfg.xml deleted file mode 100644 index e95b267..0000000 --- a/target/classes/configs/validation/hibernate.cfg.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - org.postgresql.Driver - jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber - postgres - admin - 8 - org.hibernate.dialect.PostgreSQL94Dialect - false - thread - - - - \ No newline at end of file diff --git a/target/classes/hibernate.properties b/target/classes/hibernate.properties deleted file mode 100644 index 97f7649..0000000 --- a/target/classes/hibernate.properties +++ /dev/null @@ -1,7 +0,0 @@ -hibernate.connection.driver_class = org.postgresql.Driver -hibernate.connection.url = jdbc:postgresql://localhost:5432/postgres?currentSchema=hiber -hibernate.connection.username = postgres -hibernate.connection.password = admin -hibernate.show_sql=true -hibernate.dialect = org.hibernate.dialect.PostgreSQL95Dialect -hibernate.current_session_context_class=thread \ No newline at end of file From 7336a8960b8eeee511f0af03c14486d8dcad4e94 Mon Sep 17 00:00:00 2001 From: Vyaceslav Sv Date: Wed, 27 Jan 2021 10:40:08 +0300 Subject: [PATCH 3/6] added Service layer, add productsFind method --- .gitignore | 33 +++++++ .../spring/controllers/ProductController.java | 88 +++++-------------- .../spring/servce/ProductService.java | 61 +++++++++++++ 3 files changed, 117 insertions(+), 65 deletions(-) create mode 100644 src/main/java/ru/geekbrains/spring/servce/ProductService.java diff --git a/.gitignore b/.gitignore index a1c2a23..319a5ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,36 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ # Compiled class file *.class diff --git a/src/main/java/ru/geekbrains/spring/controllers/ProductController.java b/src/main/java/ru/geekbrains/spring/controllers/ProductController.java index 0da4008..1c3488e 100644 --- a/src/main/java/ru/geekbrains/spring/controllers/ProductController.java +++ b/src/main/java/ru/geekbrains/spring/controllers/ProductController.java @@ -1,10 +1,14 @@ package ru.geekbrains.spring.controllers; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import ru.geekbrains.spring.model.Product; import ru.geekbrains.spring.reposisitory.ProductRepository; +import ru.geekbrains.spring.servce.ProductService; +import javax.persistence.criteria.CriteriaBuilder; +import java.math.BigDecimal; import java.util.List; @@ -20,28 +24,21 @@ public class ProductController { // удаление товара по id.[ GET .../app/products/delete/{id} ] + // (Замечание: пока делаем немного не по правилам REST API, эта тема будет разбираться на следующих занятиях, поэтому удаление выполняется через http-метод GET, а не DELETE) - private final ProductRepository productRepository; + private final ProductService productService; @GetMapping("/{id}") public Product getProductById(@PathVariable Long id) { - return productRepository.findById(id).get(); - } - - @GetMapping - public List findAll() { - return productRepository.findAll(); + return productService.getProductById(id); } @GetMapping("delete/{id}") - public List deleteById(@PathVariable Long id) { - productRepository.deleteById(id); - return productRepository.findAll(); + public void deleteById(@PathVariable Long id) { + productService.deleteById(id); } @PostMapping // для создания новых ресурсов public Product save(@RequestBody Product product) { - //product.setId(null); - return productRepository.save(product); + return productService.save(product); } // * К запросу всех товаров добавьте возможность фильтрации по минимальной и максимальной цене @@ -50,57 +47,18 @@ public Product save(@RequestBody Product product) { // товары дешевле max цены, + // или товары, цена которых находится в пределах min-max). + - // Методы к заданию со * : -// @GetMapping("/test") -// public List test(@RequestParam(name = "min") Integer min) { -// return productRepository.findAllByPriceGreaterThanEqual(min); -// } -// @GetMapping("/test") -// public List test(@RequestParam(name = "min") Integer min,@RequestParam(name = "max") Integer max) { -// return productRepository.findAllByPriceBetween(min,max); -// } -// @GetMapping("/test") -// public List test(@RequestParam(name = "max") Integer max) { -// return productRepository.findAllByPriceLessThanEqual(max); -// } - - + @GetMapping + public List productsFind( + @RequestParam(name = "min", required = false) Integer min, + @RequestParam(name = "max", required = false) Integer max + ) { + if (max != null && min != null) { + return productService.between(min,max); + }else if (max != null){ + return productService.less(max); + }else if (min != null){ + return productService.greater(min); + } + return productService.findAll(); + } } - - -// -//@RestController -//@RequestMapping("/students") -//public class StudentController { -// -// private final StudentRepository studentRepository; -// -// @GetMapping -// public List getAllStudents(){ -// return (List) studentRepository.findAll(); -// } -// -// @GetMapping("/{id}") -// public Student getStudentById(@PathVariable Long id){ -// return studentRepository.findById(id).get(); -// } -// -// @GetMapping("/search_by_name") -// public Student searchByName(@RequestParam String name) { -// return studentRepository.findByName(name).get(); -// } -// -// @GetMapping("/search_by_min_score") -// public List searchByMinScore(@RequestParam (name = "min_score") Integer minScore) { -// return studentRepository.findAllByScoreIsGreaterThanEqual(minScore); -// } -// @GetMapping("/test") -// public Student testMethod() { -// return studentRepository.customFinderById(2L, "Bob").get(); -// } -// -// public StudentController(StudentRepository studentRepository) { -// this.studentRepository = studentRepository; -// } -// -//} diff --git a/src/main/java/ru/geekbrains/spring/servce/ProductService.java b/src/main/java/ru/geekbrains/spring/servce/ProductService.java new file mode 100644 index 0000000..fe7e66b --- /dev/null +++ b/src/main/java/ru/geekbrains/spring/servce/ProductService.java @@ -0,0 +1,61 @@ +package ru.geekbrains.spring.servce; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.*; +import ru.geekbrains.spring.model.Product; +import ru.geekbrains.spring.reposisitory.ProductRepository; + +import java.util.List; + +@Service +public class ProductService { + + @Autowired + private final ProductRepository productRepository; + + public ProductService(ProductRepository productRepository) { + this.productRepository = productRepository; + } + + public Product getProductById(@PathVariable Long id) { + return productRepository.findById(id).get(); + } + + public List findAll() { + return productRepository.findAll(); + } + + public void deleteById(@PathVariable Long id) { + productRepository.deleteById(id); + } + + public Product save(@RequestBody Product product) { + //product.setId(null); + return productRepository.save(product); + } + +// * К запросу всех товаров добавьте возможность фильтрации по минимальной и максимальной цене +// (в трех вариантах: +// товары дороже min цены, + +// товары дешевле max цены, + +// или товары, цена которых находится в пределах min-max). + + + + @GetMapping("/greater") + public List greater(@RequestParam(name = "min") Integer min) { + return productRepository.findAllByPriceGreaterThanEqual(min); + } + + @GetMapping("/between") + public List between(@RequestParam(name = "min") Integer min, @RequestParam(name = "max") Integer max) { + return productRepository.findAllByPriceBetween(min, max); + } + + @GetMapping("/less") + public List less(@RequestParam(name = "max") Integer max) { + return productRepository.findAllByPriceLessThanEqual(max); + } + +} From 5546781d3806a9e9c91f29eab453be29822551b4 Mon Sep 17 00:00:00 2001 From: Vyaceslav Sv Date: Fri, 29 Jan 2021 11:16:36 +0300 Subject: [PATCH 4/6] lesson 08 commit --- pom.xml | 12 ++-- .../happy/market/HappyMarketApplication.java | 13 ++++ .../market/controllers/ProductController.java | 44 +++++++++++++ .../market}/model/Product.java | 4 +- .../repositories}/ProductRepository.java | 11 +--- .../happy/market/services/ProductService.java | 35 ++++++++++ .../spring/Lesson07Application.java | 13 ---- .../spring/controllers/ProductController.java | 64 ------------------- .../spring/servce/ProductService.java | 61 ------------------ src/main/resources/application.yaml | 2 +- src/main/resources/db/migration/V1__init.sql | 5 ++ .../resources/db/migration/V1__initialize.sql | 22 ------- src/main/resources/static/index.html | 42 ++++++++++++ src/main/resources/static/index.js | 32 ++++++++++ .../market/HappyMarketApplicationTests.java} | 4 +- 15 files changed, 181 insertions(+), 183 deletions(-) create mode 100644 src/main/java/ru/geekbrains/happy/market/HappyMarketApplication.java create mode 100644 src/main/java/ru/geekbrains/happy/market/controllers/ProductController.java rename src/main/java/ru/geekbrains/{spring => happy/market}/model/Product.java (90%) rename src/main/java/ru/geekbrains/{spring/reposisitory => happy/market/repositories}/ProductRepository.java (51%) create mode 100644 src/main/java/ru/geekbrains/happy/market/services/ProductService.java delete mode 100644 src/main/java/ru/geekbrains/spring/Lesson07Application.java delete mode 100644 src/main/java/ru/geekbrains/spring/controllers/ProductController.java delete mode 100644 src/main/java/ru/geekbrains/spring/servce/ProductService.java create mode 100644 src/main/resources/db/migration/V1__init.sql delete mode 100644 src/main/resources/db/migration/V1__initialize.sql create mode 100644 src/main/resources/static/index.html create mode 100644 src/main/resources/static/index.js rename src/test/java/ru/geekbrains/{spring/Lesson07ApplicationTests.java => happy/market/HappyMarketApplicationTests.java} (67%) diff --git a/pom.xml b/pom.xml index 8fbf3c1..5b3f8d6 100644 --- a/pom.xml +++ b/pom.xml @@ -8,11 +8,11 @@ 2.4.2 - ru.geekbrains.spring.lesson07 - lesson07 + ru.geekbrains.happy.market + happy-market 0.0.1-SNAPSHOT - lesson07 - Demo project for Spring Boot + happy-market + Happy Market Projekt 11 @@ -21,10 +21,6 @@ org.springframework.boot spring-boot-starter-data-jpa - - org.springframework.boot - spring-boot-starter-thymeleaf - org.springframework.boot spring-boot-starter-web diff --git a/src/main/java/ru/geekbrains/happy/market/HappyMarketApplication.java b/src/main/java/ru/geekbrains/happy/market/HappyMarketApplication.java new file mode 100644 index 0000000..1d6c1a4 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/HappyMarketApplication.java @@ -0,0 +1,13 @@ +package ru.geekbrains.happy.market; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class HappyMarketApplication { + + public static void main(String[] args) { + SpringApplication.run(HappyMarketApplication.class, args); + } + +} diff --git a/src/main/java/ru/geekbrains/happy/market/controllers/ProductController.java b/src/main/java/ru/geekbrains/happy/market/controllers/ProductController.java new file mode 100644 index 0000000..4c122b2 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/controllers/ProductController.java @@ -0,0 +1,44 @@ +package ru.geekbrains.happy.market.controllers; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import ru.geekbrains.happy.market.model.Product; +import ru.geekbrains.happy.market.services.ProductService; + +import java.util.List; + +@RestController +@RequestMapping("/products") +@RequiredArgsConstructor +public class ProductController { + private final ProductService productService; + + @GetMapping + public List findAllProducts( + @RequestParam(name = "min_price", defaultValue = "0") Integer minPrice, + @RequestParam(name = "max_price", required = false) Integer maxPrice + ) { + if (maxPrice == null) { + maxPrice = Integer.MAX_VALUE; + } + return productService.findAllByPrice(minPrice, maxPrice); + } + + @GetMapping("/{id}") + public Product findProductById(@PathVariable Long id) { + return productService.findProductById(id).get(); + } + + @PostMapping + public Product saveNewProduct(@RequestBody Product product) { + product.setId(null); + return productService.saveOrUpdate(product); + } + + @DeleteMapping + public void deleteProductById(@RequestParam Long id) { + productService.deleteProductById(id); + } + + +} diff --git a/src/main/java/ru/geekbrains/spring/model/Product.java b/src/main/java/ru/geekbrains/happy/market/model/Product.java similarity index 90% rename from src/main/java/ru/geekbrains/spring/model/Product.java rename to src/main/java/ru/geekbrains/happy/market/model/Product.java index e3689b4..bef3973 100644 --- a/src/main/java/ru/geekbrains/spring/model/Product.java +++ b/src/main/java/ru/geekbrains/happy/market/model/Product.java @@ -1,5 +1,4 @@ -package ru.geekbrains.spring.model; - +package ru.geekbrains.happy.market.model; import lombok.Data; import lombok.NoArgsConstructor; @@ -21,5 +20,4 @@ public class Product { @Column(name = "price") private int price; - } diff --git a/src/main/java/ru/geekbrains/spring/reposisitory/ProductRepository.java b/src/main/java/ru/geekbrains/happy/market/repositories/ProductRepository.java similarity index 51% rename from src/main/java/ru/geekbrains/spring/reposisitory/ProductRepository.java rename to src/main/java/ru/geekbrains/happy/market/repositories/ProductRepository.java index 285208e..ba8173a 100644 --- a/src/main/java/ru/geekbrains/spring/reposisitory/ProductRepository.java +++ b/src/main/java/ru/geekbrains/happy/market/repositories/ProductRepository.java @@ -1,19 +1,12 @@ -package ru.geekbrains.spring.reposisitory; +package ru.geekbrains.happy.market.repositories; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; -import ru.geekbrains.spring.model.Product; +import ru.geekbrains.happy.market.model.Product; import java.util.List; @Repository public interface ProductRepository extends JpaRepository { - - List findAllByPriceGreaterThanEqual(int min); - - List findAllByPriceLessThanEqual(int max); - List findAllByPriceBetween(int min, int max); - } diff --git a/src/main/java/ru/geekbrains/happy/market/services/ProductService.java b/src/main/java/ru/geekbrains/happy/market/services/ProductService.java new file mode 100644 index 0000000..b063db6 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/services/ProductService.java @@ -0,0 +1,35 @@ +package ru.geekbrains.happy.market.services; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.geekbrains.happy.market.model.Product; +import ru.geekbrains.happy.market.repositories.ProductRepository; + +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class ProductService { + private final ProductRepository productRepository; + + public Optional findProductById(Long id) { + return productRepository.findById(id); + } + + public List findAll() { + return productRepository.findAll(); + } + + public List findAllByPrice(int min, int max) { + return productRepository.findAllByPriceBetween(min, max); + } + + public Product saveOrUpdate(Product product) { + return productRepository.save(product); + } + + public void deleteProductById(Long id) { + productRepository.deleteById(id); + } +} diff --git a/src/main/java/ru/geekbrains/spring/Lesson07Application.java b/src/main/java/ru/geekbrains/spring/Lesson07Application.java deleted file mode 100644 index 2b7cb09..0000000 --- a/src/main/java/ru/geekbrains/spring/Lesson07Application.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.geekbrains.spring; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class Lesson07Application { - - public static void main(String[] args) { - SpringApplication.run(Lesson07Application.class, args); - } - -} diff --git a/src/main/java/ru/geekbrains/spring/controllers/ProductController.java b/src/main/java/ru/geekbrains/spring/controllers/ProductController.java deleted file mode 100644 index 1c3488e..0000000 --- a/src/main/java/ru/geekbrains/spring/controllers/ProductController.java +++ /dev/null @@ -1,64 +0,0 @@ -package ru.geekbrains.spring.controllers; - -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; -import ru.geekbrains.spring.model.Product; -import ru.geekbrains.spring.reposisitory.ProductRepository; -import ru.geekbrains.spring.servce.ProductService; - -import javax.persistence.criteria.CriteriaBuilder; -import java.math.BigDecimal; -import java.util.List; - - -@RestController -@RequestMapping("/products") -@RequiredArgsConstructor -public class ProductController { - -// Сделать RestController позволяющий выполнять следующий набор операции над этой сущностью: -// получение товара по id [ GET .../app/products/{id} ] + -// получение всех товаров [ GET .../app/products ] + -// создание нового товара [ POST .../app/products ] + -// удаление товара по id.[ GET .../app/products/delete/{id} ] + -// (Замечание: пока делаем немного не по правилам REST API, эта тема будет разбираться на следующих занятиях, поэтому удаление выполняется через http-метод GET, а не DELETE) - - private final ProductService productService; - - @GetMapping("/{id}") - public Product getProductById(@PathVariable Long id) { - return productService.getProductById(id); - } - - @GetMapping("delete/{id}") - public void deleteById(@PathVariable Long id) { - productService.deleteById(id); - } - - @PostMapping // для создания новых ресурсов - public Product save(@RequestBody Product product) { - return productService.save(product); - } - -// * К запросу всех товаров добавьте возможность фильтрации по минимальной и максимальной цене -// (в трех вариантах: -// товары дороже min цены, + -// товары дешевле max цены, + -// или товары, цена которых находится в пределах min-max). + - - @GetMapping - public List productsFind( - @RequestParam(name = "min", required = false) Integer min, - @RequestParam(name = "max", required = false) Integer max - ) { - if (max != null && min != null) { - return productService.between(min,max); - }else if (max != null){ - return productService.less(max); - }else if (min != null){ - return productService.greater(min); - } - return productService.findAll(); - } -} diff --git a/src/main/java/ru/geekbrains/spring/servce/ProductService.java b/src/main/java/ru/geekbrains/spring/servce/ProductService.java deleted file mode 100644 index fe7e66b..0000000 --- a/src/main/java/ru/geekbrains/spring/servce/ProductService.java +++ /dev/null @@ -1,61 +0,0 @@ -package ru.geekbrains.spring.servce; - - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.web.bind.annotation.*; -import ru.geekbrains.spring.model.Product; -import ru.geekbrains.spring.reposisitory.ProductRepository; - -import java.util.List; - -@Service -public class ProductService { - - @Autowired - private final ProductRepository productRepository; - - public ProductService(ProductRepository productRepository) { - this.productRepository = productRepository; - } - - public Product getProductById(@PathVariable Long id) { - return productRepository.findById(id).get(); - } - - public List findAll() { - return productRepository.findAll(); - } - - public void deleteById(@PathVariable Long id) { - productRepository.deleteById(id); - } - - public Product save(@RequestBody Product product) { - //product.setId(null); - return productRepository.save(product); - } - -// * К запросу всех товаров добавьте возможность фильтрации по минимальной и максимальной цене -// (в трех вариантах: -// товары дороже min цены, + -// товары дешевле max цены, + -// или товары, цена которых находится в пределах min-max). + - - - @GetMapping("/greater") - public List greater(@RequestParam(name = "min") Integer min) { - return productRepository.findAllByPriceGreaterThanEqual(min); - } - - @GetMapping("/between") - public List between(@RequestParam(name = "min") Integer min, @RequestParam(name = "max") Integer max) { - return productRepository.findAllByPriceBetween(min, max); - } - - @GetMapping("/less") - public List less(@RequestParam(name = "max") Integer max) { - return productRepository.findAllByPriceLessThanEqual(max); - } - -} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 90aeb98..475da03 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,7 +1,7 @@ server: port: 8189 servlet: - context-path: /app + context-path: /happy spring: datasource: driver-class-name: org.h2.Driver diff --git a/src/main/resources/db/migration/V1__init.sql b/src/main/resources/db/migration/V1__init.sql new file mode 100644 index 0000000..10a099b --- /dev/null +++ b/src/main/resources/db/migration/V1__init.sql @@ -0,0 +1,5 @@ +create table products (id bigserial primary key, title varchar(255), price int); +insert into products (title, price) values +('Bread', 10), +('Milk', 27), +('Cheese', 924); \ No newline at end of file diff --git a/src/main/resources/db/migration/V1__initialize.sql b/src/main/resources/db/migration/V1__initialize.sql deleted file mode 100644 index 04d53be..0000000 --- a/src/main/resources/db/migration/V1__initialize.sql +++ /dev/null @@ -1,22 +0,0 @@ -create table products (id bigserial primary key, title varchar (255), price int); -insert into products (title, price) values -('product_1',100), -('product_2',200), -('product_3',300), -('product_4',400), -('product_5',500), -('product_6',600), -('product_7',700), -('product_8',800), -('product_9',900), -('product_10',1000), -('product_11',1100), -('product_12',1200), -('product_13',1300), -('product_14',1400), -('product_15',1500), -('product_16',1600), -('product_17',1700), -('product_18',1800), -('product_19',1900), -('product_20',2000); diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html new file mode 100644 index 0000000..a97be2d --- /dev/null +++ b/src/main/resources/static/index.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + +
+

Happy Market

+

Список товаров

+ + + + + + + + + + + + + + + +
НазваниеЦена
{{p.title}}{{p.price}} +
+ +
+
+ + + \ No newline at end of file diff --git a/src/main/resources/static/index.js b/src/main/resources/static/index.js new file mode 100644 index 0000000..a59842f --- /dev/null +++ b/src/main/resources/static/index.js @@ -0,0 +1,32 @@ +angular.module('app', []).controller('indexController', function ($scope, $http) { + const contextPath = 'http://localhost:8189/happy'; + + $scope.fillTable = function () { + $http({ + url: contextPath + '/products', + method: 'GET', + params: { + min_price: $scope.filter ? $scope.filter.min_price : null, + max_price: $scope.filter ? $scope.filter.max_price : null + } + }).then(function (response) { + $scope.ProductsList = response.data; + }); + }; + + $scope.deleteProductById = function (id) { + $http({ + url: contextPath + '/products', + method: 'DELETE', + params: { + id: id + } + }).then(function (response) { + $scope.fillTable(); + }); + }; + + + $scope.fillTable(); +}); + diff --git a/src/test/java/ru/geekbrains/spring/Lesson07ApplicationTests.java b/src/test/java/ru/geekbrains/happy/market/HappyMarketApplicationTests.java similarity index 67% rename from src/test/java/ru/geekbrains/spring/Lesson07ApplicationTests.java rename to src/test/java/ru/geekbrains/happy/market/HappyMarketApplicationTests.java index 7342674..3555bd7 100644 --- a/src/test/java/ru/geekbrains/spring/Lesson07ApplicationTests.java +++ b/src/test/java/ru/geekbrains/happy/market/HappyMarketApplicationTests.java @@ -1,10 +1,10 @@ -package ru.geekbrains.spring; +package ru.geekbrains.happy.market; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest -class Lesson07ApplicationTests { +class HappyMarketApplicationTests { @Test void contextLoads() { From dc6c0434ae9dc29c3df227bcc8c437f1716ffb81 Mon Sep 17 00:00:00 2001 From: Vyaceslav Sv Date: Sun, 31 Jan 2021 13:53:37 +0300 Subject: [PATCH 5/6] lesson 09 created commit --- .../happy/market/HappyMarketApplication.java | 8 +-- .../market/controllers/ProductController.java | 42 ++++++++---- .../happy/market/dto/ProductDto.java | 22 ++++++ .../happy/market/model/Product.java | 13 ++++ .../happy/market/services/ProductService.java | 35 ++++++++-- src/main/resources/db/migration/V1__init.sql | 16 ++++- src/main/resources/static/index.html | 67 +++++++++++++++++-- src/main/resources/static/index.js | 49 +++++++++----- 8 files changed, 207 insertions(+), 45 deletions(-) create mode 100644 src/main/java/ru/geekbrains/happy/market/dto/ProductDto.java diff --git a/src/main/java/ru/geekbrains/happy/market/HappyMarketApplication.java b/src/main/java/ru/geekbrains/happy/market/HappyMarketApplication.java index 1d6c1a4..23c3641 100644 --- a/src/main/java/ru/geekbrains/happy/market/HappyMarketApplication.java +++ b/src/main/java/ru/geekbrains/happy/market/HappyMarketApplication.java @@ -5,9 +5,7 @@ @SpringBootApplication public class HappyMarketApplication { - - public static void main(String[] args) { - SpringApplication.run(HappyMarketApplication.class, args); - } - + public static void main(String[] args) { + SpringApplication.run(HappyMarketApplication.class, args); + } } diff --git a/src/main/java/ru/geekbrains/happy/market/controllers/ProductController.java b/src/main/java/ru/geekbrains/happy/market/controllers/ProductController.java index 4c122b2..032953a 100644 --- a/src/main/java/ru/geekbrains/happy/market/controllers/ProductController.java +++ b/src/main/java/ru/geekbrains/happy/market/controllers/ProductController.java @@ -1,44 +1,62 @@ package ru.geekbrains.happy.market.controllers; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; +import ru.geekbrains.happy.market.dto.ProductDto; import ru.geekbrains.happy.market.model.Product; import ru.geekbrains.happy.market.services.ProductService; -import java.util.List; - @RestController -@RequestMapping("/products") +@RequestMapping("/api/v1/products") @RequiredArgsConstructor public class ProductController { private final ProductService productService; @GetMapping - public List findAllProducts( + public Page findAllProducts( @RequestParam(name = "min_price", defaultValue = "0") Integer minPrice, - @RequestParam(name = "max_price", required = false) Integer maxPrice + @RequestParam(name = "max_price", required = false) Integer maxPrice, + @RequestParam(name = "title", required = false) String title, + @RequestParam(name = "p", defaultValue = "1") Integer page ) { - if (maxPrice == null) { - maxPrice = Integer.MAX_VALUE; + if (page < 1) { + page = 1; } - return productService.findAllByPrice(minPrice, maxPrice); +// if (maxPrice != null) { +// return productService.findAllByPrice(minPrice,maxPrice); +// } + + return productService.findAll(page); } @GetMapping("/{id}") - public Product findProductById(@PathVariable Long id) { + public ProductDto findProductById(@PathVariable Long id) { return productService.findProductById(id).get(); } +// @GetMapping("/{id}") +// public Product findProductById(@PathVariable Long id) { +// return productService.findProductById(id).get(); +// } + // - @PostMapping + @ResponseStatus(HttpStatus.CREATED) public Product saveNewProduct(@RequestBody Product product) { product.setId(null); return productService.saveOrUpdate(product); } - @DeleteMapping - public void deleteProductById(@RequestParam Long id) { - productService.deleteProductById(id); + // - + @PutMapping + public Product updateProduct(@RequestBody Product product) { + return productService.saveOrUpdate(product); } + @DeleteMapping("/{id}") + public void deleteProduct(@PathVariable Long id) { + productService.deleteById(id); + } } diff --git a/src/main/java/ru/geekbrains/happy/market/dto/ProductDto.java b/src/main/java/ru/geekbrains/happy/market/dto/ProductDto.java new file mode 100644 index 0000000..7bbd4fc --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/dto/ProductDto.java @@ -0,0 +1,22 @@ +package ru.geekbrains.happy.market.dto; + + +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.geekbrains.happy.market.model.Product; + +@Data +@NoArgsConstructor +public class ProductDto { + + private Long id; + private String title; + private int price; + + public ProductDto(Product p) { + this.id = p.getId(); + this.title = p.getTitle(); + this.price = p.getPrice(); + } + +} diff --git a/src/main/java/ru/geekbrains/happy/market/model/Product.java b/src/main/java/ru/geekbrains/happy/market/model/Product.java index bef3973..3618724 100644 --- a/src/main/java/ru/geekbrains/happy/market/model/Product.java +++ b/src/main/java/ru/geekbrains/happy/market/model/Product.java @@ -2,8 +2,11 @@ import lombok.Data; import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; import javax.persistence.*; +import java.time.LocalDateTime; @Entity @Table(name = "products") @@ -20,4 +23,14 @@ public class Product { @Column(name = "price") private int price; + + @Column(name = "created_at") + @CreationTimestamp + private LocalDateTime createAt; + + @Column(name = "updated_at") + @UpdateTimestamp + private LocalDateTime updateAt; + + } diff --git a/src/main/java/ru/geekbrains/happy/market/services/ProductService.java b/src/main/java/ru/geekbrains/happy/market/services/ProductService.java index b063db6..4018ede 100644 --- a/src/main/java/ru/geekbrains/happy/market/services/ProductService.java +++ b/src/main/java/ru/geekbrains/happy/market/services/ProductService.java @@ -1,35 +1,56 @@ package ru.geekbrains.happy.market.services; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; +import ru.geekbrains.happy.market.dto.ProductDto; import ru.geekbrains.happy.market.model.Product; import ru.geekbrains.happy.market.repositories.ProductRepository; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class ProductService { private final ProductRepository productRepository; - public Optional findProductById(Long id) { - return productRepository.findById(id); + + // + + public Optional findProductById(Long id) { + return productRepository.findById(id).stream().map(ProductDto::new).findFirst(); + } +// public Optional findProductById(Long id) { +// return productRepository.findById(id); +// } + + public List findAll() { + return productRepository.findAll().stream().map(ProductDto::new).collect(Collectors.toList()); } - public List findAll() { - return productRepository.findAll(); + public Page findAll(int page) { + Page originalPage = productRepository.findAll(PageRequest.of(page - 1, 5)); + return new PageImpl<>(originalPage.getContent().stream().map(ProductDto::new).collect(Collectors.toList()), originalPage.getPageable(), originalPage.getTotalElements()); } - public List findAllByPrice(int min, int max) { - return productRepository.findAllByPriceBetween(min, max); + // + + public List findAllByPrice(int min, int max) { + return productRepository.findAllByPriceBetween(min, max).stream().map(ProductDto::new).collect(Collectors.toList()); } +// public List findAllByPrice(int min, int max) { +// return productRepository.findAllByPriceBetween(min, max); +// } + // - public Product saveOrUpdate(Product product) { return productRepository.save(product); } - public void deleteProductById(Long id) { + // - + public void deleteById(Long id) { productRepository.deleteById(id); } } diff --git a/src/main/resources/db/migration/V1__init.sql b/src/main/resources/db/migration/V1__init.sql index 10a099b..76c6926 100644 --- a/src/main/resources/db/migration/V1__init.sql +++ b/src/main/resources/db/migration/V1__init.sql @@ -1,5 +1,17 @@ -create table products (id bigserial primary key, title varchar(255), price int); +create table products ( + id bigserial primary key, + title varchar(255), + price int, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp + ); insert into products (title, price) values ('Bread', 10), ('Milk', 27), -('Cheese', 924); \ No newline at end of file +('Cheese1', 94), +('Cheese2', 974), +('Cheese3', 964), +('Cheese4', 954), +('Cheese5', 94), +('Cheese6', 2924), +('Cheese7', 394); \ No newline at end of file diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index a97be2d..98a0879 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -16,8 +16,36 @@
\ No newline at end of file diff --git a/src/main/resources/static/index.js b/src/main/resources/static/index.js index a59842f..b11e637 100644 --- a/src/main/resources/static/index.js +++ b/src/main/resources/static/index.js @@ -1,32 +1,51 @@ angular.module('app', []).controller('indexController', function ($scope, $http) { - const contextPath = 'http://localhost:8189/happy'; + const contextPath = 'http://localhost:8189/happy/api/v1'; - $scope.fillTable = function () { + + $scope.fillTable = function (pageIndex = 1) { $http({ url: contextPath + '/products', method: 'GET', params: { + title: $scope.filter ? $scope.filter.title : null, min_price: $scope.filter ? $scope.filter.min_price : null, - max_price: $scope.filter ? $scope.filter.max_price : null + max_price: $scope.filter ? $scope.filter.max_price : null, + p: pageIndex } }).then(function (response) { - $scope.ProductsList = response.data; + $scope.ProductsPage = response.data; + $scope.PaginationArray = $scope.generatePageIndexes(1, $scope.ProductsPage.totalPages); }); }; - $scope.deleteProductById = function (id) { - $http({ - url: contextPath + '/products', - method: 'DELETE', - params: { - id: id - } - }).then(function (response) { - $scope.fillTable(); - }); + + $scope.generatePageIndexes = function (startPage, endPage) { + let arr = []; + for (let i = startPage; i < endPage + 1; i++) { + arr.push(i); + } + return arr; + } + + + $scope.submitCreateNewProduct = function () { + $http.post(contextPath + '/products', $scope.newProduct) + .then(function (response) { + $scope.newProduct = null; + $scope.fillTable(); + }); }; - $scope.fillTable(); + $scope.deleteProductById = function (productId) { + $http.delete(contextPath + '/products/' + productId).then(function (response) { + $scope.fillTable(); + }) + } + + + $scope.fillTable() + }); + From 60dc6181a4344763b6d0850f95d0c36633f2e2ed Mon Sep 17 00:00:00 2001 From: Vyaceslav Sv Date: Sat, 6 Feb 2021 19:17:33 +0300 Subject: [PATCH 6/6] lesson2.01 --- pom.xml | 17 +- questions.txt | 12 ++ .../happy/market/HappyMarketApplication.java | 14 ++ .../geekbrains/happy/market/beans/Cart.java | 57 ++++++ .../happy/market/beans/JwtTokenUtil.java | 60 ++++++ .../market/configs/JwtRequestFilter.java | 57 ++++++ .../happy/market/configs/SecurityConfig.java | 47 +++++ .../market/controllers/AuthController.java | 37 ++++ .../market/controllers/CartController.java | 33 ++++ .../market/controllers/ProductController.java | 31 ++- .../geekbrains/happy/market/dto/CartDto.java | 20 ++ .../happy/market/dto/JwtRequest.java | 9 + .../happy/market/dto/JwtResponse.java | 10 + .../happy/market/dto/OrderItemDto.java | 22 +++ .../happy/market/dto/ProductDto.java | 3 - .../ExceptionControllerAdvice.java | 18 ++ .../exceptions_handling/MarketError.java | 18 ++ .../ResourceNotFoundException.java | 7 + .../happy/market/model/OrderItem.java | 47 +++++ .../happy/market/model/Product.java | 6 +- .../geekbrains/happy/market/model/Role.java | 29 +++ .../geekbrains/happy/market/model/User.java | 42 ++++ .../repositories/ProductRepository.java | 4 +- .../market/repositories/UserRepository.java | 12 ++ .../specifications/ProductSpecifications.java | 40 ++++ .../happy/market/services/ProductService.java | 29 +-- .../happy/market/services/UserService.java | 38 ++++ src/main/resources/db/migration/V1__init.sql | 87 +++++++-- src/main/resources/secured.properties | 1 + src/main/resources/static/index.html | 181 ++++++++++-------- src/main/resources/static/index.js | 83 ++++++-- 31 files changed, 909 insertions(+), 162 deletions(-) create mode 100644 questions.txt create mode 100644 src/main/java/ru/geekbrains/happy/market/beans/Cart.java create mode 100644 src/main/java/ru/geekbrains/happy/market/beans/JwtTokenUtil.java create mode 100644 src/main/java/ru/geekbrains/happy/market/configs/JwtRequestFilter.java create mode 100644 src/main/java/ru/geekbrains/happy/market/configs/SecurityConfig.java create mode 100644 src/main/java/ru/geekbrains/happy/market/controllers/AuthController.java create mode 100644 src/main/java/ru/geekbrains/happy/market/controllers/CartController.java create mode 100644 src/main/java/ru/geekbrains/happy/market/dto/CartDto.java create mode 100644 src/main/java/ru/geekbrains/happy/market/dto/JwtRequest.java create mode 100644 src/main/java/ru/geekbrains/happy/market/dto/JwtResponse.java create mode 100644 src/main/java/ru/geekbrains/happy/market/dto/OrderItemDto.java create mode 100644 src/main/java/ru/geekbrains/happy/market/exceptions_handling/ExceptionControllerAdvice.java create mode 100644 src/main/java/ru/geekbrains/happy/market/exceptions_handling/MarketError.java create mode 100644 src/main/java/ru/geekbrains/happy/market/exceptions_handling/ResourceNotFoundException.java create mode 100644 src/main/java/ru/geekbrains/happy/market/model/OrderItem.java create mode 100644 src/main/java/ru/geekbrains/happy/market/model/Role.java create mode 100644 src/main/java/ru/geekbrains/happy/market/model/User.java create mode 100644 src/main/java/ru/geekbrains/happy/market/repositories/UserRepository.java create mode 100644 src/main/java/ru/geekbrains/happy/market/repositories/specifications/ProductSpecifications.java create mode 100644 src/main/java/ru/geekbrains/happy/market/services/UserService.java create mode 100644 src/main/resources/secured.properties diff --git a/pom.xml b/pom.xml index 5b3f8d6..a63e482 100644 --- a/pom.xml +++ b/pom.xml @@ -5,17 +5,19 @@ org.springframework.boot spring-boot-starter-parent - 2.4.2 + 2.4.1 ru.geekbrains.happy.market happy-market 0.0.1-SNAPSHOT happy-market - Happy Market Projekt + Happy Market Project + 11 + org.springframework.boot @@ -25,6 +27,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-security + org.flywaydb flyway-core @@ -41,6 +47,12 @@ true + + io.jsonwebtoken + jjwt + 0.9.1 + + org.springframework.boot spring-boot-starter-test test @@ -63,5 +75,4 @@ - diff --git a/questions.txt b/questions.txt new file mode 100644 index 0000000..d958b45 --- /dev/null +++ b/questions.txt @@ -0,0 +1,12 @@ +1. Чем по-умолчанию инициализируются локальные переменные? +2. Для сего нужны пакеты в Java? +3. Что такое статический импорт? +4. Что делает ключевое слово package? +5. Что такое инкапсуляция, полиморфизм и наследование? +6. Что такое абстрактный класс? +7. Что такое интерфейсы? Функциональные интерфейсы? Какие ФИ вы знаете? +Бывают ли пустые интерфейсы? +8. При импорте лкассов из пакета mypack.* будут ли импортированы классы из +пдопакетов? +9. Что такое раннее и позднее связывание? +10. \ No newline at end of file diff --git a/src/main/java/ru/geekbrains/happy/market/HappyMarketApplication.java b/src/main/java/ru/geekbrains/happy/market/HappyMarketApplication.java index 23c3641..6637c17 100644 --- a/src/main/java/ru/geekbrains/happy/market/HappyMarketApplication.java +++ b/src/main/java/ru/geekbrains/happy/market/HappyMarketApplication.java @@ -2,9 +2,23 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.PropertySource; + +import java.util.stream.Collectors; +import java.util.stream.Stream; @SpringBootApplication +@PropertySource("classpath:secured.properties") public class HappyMarketApplication { + // Домашнее задание: + // +- // $scope.user.username = null; -- удалось сделать, только закомментировав обнуление имени, как достать имя из токена не понял. + // + // 1. Если пользователь авторизован показываем в шапке какую-нибудь информацию о нем (имя), + // если нет, там же в шапке должны боть поля логин/пароль и кнопка войти + // + // 2. Попробуйте сделать оформление заказа с привязкой к текущему пользователю + // (делать адрес доставки или что-то еще не требуется) + public static void main(String[] args) { SpringApplication.run(HappyMarketApplication.class, args); } diff --git a/src/main/java/ru/geekbrains/happy/market/beans/Cart.java b/src/main/java/ru/geekbrains/happy/market/beans/Cart.java new file mode 100644 index 0000000..a0d416b --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/beans/Cart.java @@ -0,0 +1,57 @@ +package ru.geekbrains.happy.market.beans; + +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.stereotype.Component; +import org.springframework.web.context.WebApplicationContext; +import ru.geekbrains.happy.market.exceptions_handling.ResourceNotFoundException; +import ru.geekbrains.happy.market.model.OrderItem; +import ru.geekbrains.happy.market.model.Product; +import ru.geekbrains.happy.market.services.ProductService; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; + +@Component +@RequiredArgsConstructor +@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS) +@Data +public class Cart { + private final ProductService productService; + private List items; + private int totalPrice; + + @PostConstruct + public void init() { + this.items = new ArrayList<>(); + } + + public void addToCart(Long id) { + for (OrderItem o : items) { + if (o.getProduct().getId().equals(id)) { + o.incrementQuantity(); + recalculate(); + return; + } + } + Product p = productService.findProductById(id).orElseThrow(() -> new ResourceNotFoundException("Unable to find product with id: " + id + " (add to cart)")); + OrderItem orderItem = new OrderItem(p); + items.add(orderItem); + recalculate(); + } + + public void clear() { + items.clear(); + recalculate(); + } + + public void recalculate() { + totalPrice = 0; + for (OrderItem o : items) { + totalPrice += o.getPrice(); + } + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/beans/JwtTokenUtil.java b/src/main/java/ru/geekbrains/happy/market/beans/JwtTokenUtil.java new file mode 100644 index 0000000..7cd6902 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/beans/JwtTokenUtil.java @@ -0,0 +1,60 @@ +package ru.geekbrains.happy.market.beans; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Component +public class JwtTokenUtil { + @Value("${jwt.secret}") + private String secret; + + public String generateToken(UserDetails userDetails) { + Map claims = new HashMap<>(); + List rolesList = userDetails.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.toList()); + claims.put("roles", rolesList); + + Date issuedDate = new Date(); + Date expiredDate = new Date(issuedDate.getTime() + 20 * 60 * 1000); // todo + return Jwts.builder() + .setClaims(claims) + .setSubject(userDetails.getUsername()) + .setIssuedAt(issuedDate) + .setExpiration(expiredDate) + .signWith(SignatureAlgorithm.HS256, secret) + .compact(); + } + + public String getUsernameFromToken(String token) { + return getClaimFromToken(token, Claims::getSubject); + } + + public List getRoles(String token) { + return getClaimFromToken(token, (Function>) claims -> claims.get("roles", List.class)); + } + + private T getClaimFromToken(String token, Function claimsResolver) { + Claims claims = getAllClaimsFromToken(token); + return claimsResolver.apply(claims); + } + + private Claims getAllClaimsFromToken(String token) { + return Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/configs/JwtRequestFilter.java b/src/main/java/ru/geekbrains/happy/market/configs/JwtRequestFilter.java new file mode 100644 index 0000000..7819842 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/configs/JwtRequestFilter.java @@ -0,0 +1,57 @@ +package ru.geekbrains.happy.market.configs; + +import io.jsonwebtoken.ExpiredJwtException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import ru.geekbrains.happy.market.beans.JwtTokenUtil; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +@Slf4j +public class JwtRequestFilter extends OncePerRequestFilter { +// private final UserDetailsService userDetailsService; + private final JwtTokenUtil jwtTokenUtil; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String authHeader = request.getHeader("Authorization"); + + String username = null; + String jwt = null; + if (authHeader != null && authHeader.startsWith("Bearer ")) { + jwt = authHeader.substring(7); + try { + username = jwtTokenUtil.getUsernameFromToken(jwt); + } catch (ExpiredJwtException e) { + log.debug("The token is expired"); +// String error = JsonUtils.convertObjectToJson(new BookServiceError(HttpStatus.UNAUTHORIZED.value(), "Jwt is expired")); +// response.sendError(HttpServletResponse.SC_UNAUTHORIZED, error); +// return; + } + } + + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { +// UserDetails userDetails = userDetailsService.loadUserByUsername(username); +// UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); +// token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); +// SecurityContextHolder.getContext().setAuthentication(token); + + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, jwtTokenUtil.getRoles(jwt).stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())); + SecurityContextHolder.getContext().setAuthentication(token); + } + + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/configs/SecurityConfig.java b/src/main/java/ru/geekbrains/happy/market/configs/SecurityConfig.java new file mode 100644 index 0000000..7b17581 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/configs/SecurityConfig.java @@ -0,0 +1,47 @@ +package ru.geekbrains.happy.market.configs; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@EnableWebSecurity +@RequiredArgsConstructor +@Slf4j +public class SecurityConfig extends WebSecurityConfigurerAdapter { + private final JwtRequestFilter jwtRequestFilter; + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .csrf().disable() + .authorizeRequests() + .antMatchers("/api/v1/**").authenticated() + .antMatchers("/h2-console/**").permitAll() + .anyRequest().permitAll() + .and() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .headers().frameOptions().disable(); + http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); + } + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + @Bean + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/controllers/AuthController.java b/src/main/java/ru/geekbrains/happy/market/controllers/AuthController.java new file mode 100644 index 0000000..37e5873 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/controllers/AuthController.java @@ -0,0 +1,37 @@ +package ru.geekbrains.happy.market.controllers; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import ru.geekbrains.happy.market.beans.JwtTokenUtil; +import ru.geekbrains.happy.market.dto.JwtRequest; +import ru.geekbrains.happy.market.dto.JwtResponse; +import ru.geekbrains.happy.market.exceptions_handling.MarketError; +import ru.geekbrains.happy.market.services.UserService; + +@RestController +@RequiredArgsConstructor +public class AuthController { + private final UserService userService; + private final JwtTokenUtil jwtTokenUtil; + private final AuthenticationManager authenticationManager; + + @PostMapping("/auth") + public ResponseEntity createAuthToken(@RequestBody JwtRequest authRequest) { + try { + authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())); + } catch (BadCredentialsException ex) { + return new ResponseEntity<>(new MarketError(HttpStatus.UNAUTHORIZED.value(), "Incorrect username or password"), HttpStatus.UNAUTHORIZED); + } + UserDetails userDetails = userService.loadUserByUsername(authRequest.getUsername()); + String token = jwtTokenUtil.generateToken(userDetails); + return ResponseEntity.ok(new JwtResponse(token)); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/controllers/CartController.java b/src/main/java/ru/geekbrains/happy/market/controllers/CartController.java new file mode 100644 index 0000000..a0eea5c --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/controllers/CartController.java @@ -0,0 +1,33 @@ +package ru.geekbrains.happy.market.controllers; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.geekbrains.happy.market.beans.Cart; +import ru.geekbrains.happy.market.dto.CartDto; + +import java.security.Principal; + +@RestController +@RequestMapping("/api/v1/cart") +@RequiredArgsConstructor +public class CartController { + private final Cart cart; + + @GetMapping + public CartDto getCart() { + return new CartDto(cart); + } + + @GetMapping("/add/{id}") + public void addToCart(@PathVariable Long id) { + cart.addToCart(id); + } + + @GetMapping("/clear") + public void clearCart() { + cart.clear(); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/controllers/ProductController.java b/src/main/java/ru/geekbrains/happy/market/controllers/ProductController.java index 032953a..627a4dc 100644 --- a/src/main/java/ru/geekbrains/happy/market/controllers/ProductController.java +++ b/src/main/java/ru/geekbrains/happy/market/controllers/ProductController.java @@ -2,12 +2,23 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; +import org.springframework.data.jpa.domain.Specification; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.*; import ru.geekbrains.happy.market.dto.ProductDto; +import ru.geekbrains.happy.market.exceptions_handling.MarketError; +import ru.geekbrains.happy.market.exceptions_handling.ResourceNotFoundException; import ru.geekbrains.happy.market.model.Product; +import ru.geekbrains.happy.market.repositories.ProductRepository; +import ru.geekbrains.happy.market.repositories.specifications.ProductSpecifications; import ru.geekbrains.happy.market.services.ProductService; +import java.util.List; +import java.util.Map; +import java.util.Optional; + @RestController @RequestMapping("/api/v1/products") @RequiredArgsConstructor @@ -16,31 +27,21 @@ public class ProductController { @GetMapping public Page findAllProducts( - @RequestParam(name = "min_price", defaultValue = "0") Integer minPrice, - @RequestParam(name = "max_price", required = false) Integer maxPrice, - @RequestParam(name = "title", required = false) String title, + @RequestParam MultiValueMap params, @RequestParam(name = "p", defaultValue = "1") Integer page ) { if (page < 1) { page = 1; } -// if (maxPrice != null) { -// return productService.findAllByPrice(minPrice,maxPrice); -// } - return productService.findAll(page); + return productService.findAll(ProductSpecifications.build(params), page, 4); } @GetMapping("/{id}") public ProductDto findProductById(@PathVariable Long id) { - return productService.findProductById(id).get(); + return productService.findProductDtoById(id).orElseThrow(() -> new ResourceNotFoundException("Product with id: " + id + " doens't exist")); } -// @GetMapping("/{id}") -// public Product findProductById(@PathVariable Long id) { -// return productService.findProductById(id).get(); -// } - // - @PostMapping @ResponseStatus(HttpStatus.CREATED) public Product saveNewProduct(@RequestBody Product product) { @@ -48,15 +49,13 @@ public Product saveNewProduct(@RequestBody Product product) { return productService.saveOrUpdate(product); } - // - @PutMapping public Product updateProduct(@RequestBody Product product) { return productService.saveOrUpdate(product); } @DeleteMapping("/{id}") - public void deleteProduct(@PathVariable Long id) { + public void updateProduct(@PathVariable Long id) { productService.deleteById(id); } - } diff --git a/src/main/java/ru/geekbrains/happy/market/dto/CartDto.java b/src/main/java/ru/geekbrains/happy/market/dto/CartDto.java new file mode 100644 index 0000000..aa2b7d6 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/dto/CartDto.java @@ -0,0 +1,20 @@ +package ru.geekbrains.happy.market.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.geekbrains.happy.market.beans.Cart; + +import java.util.List; +import java.util.stream.Collectors; + +@NoArgsConstructor +@Data +public class CartDto { + private List items; + private int totalPrice; + + public CartDto(Cart cart) { + this.totalPrice = cart.getTotalPrice(); + this.items = cart.getItems().stream().map(OrderItemDto::new).collect(Collectors.toList()); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/dto/JwtRequest.java b/src/main/java/ru/geekbrains/happy/market/dto/JwtRequest.java new file mode 100644 index 0000000..c3807ce --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/dto/JwtRequest.java @@ -0,0 +1,9 @@ +package ru.geekbrains.happy.market.dto; + +import lombok.Data; + +@Data +public class JwtRequest { + private String username; + private String password; +} diff --git a/src/main/java/ru/geekbrains/happy/market/dto/JwtResponse.java b/src/main/java/ru/geekbrains/happy/market/dto/JwtResponse.java new file mode 100644 index 0000000..884304b --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/dto/JwtResponse.java @@ -0,0 +1,10 @@ +package ru.geekbrains.happy.market.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class JwtResponse { + private String token; +} diff --git a/src/main/java/ru/geekbrains/happy/market/dto/OrderItemDto.java b/src/main/java/ru/geekbrains/happy/market/dto/OrderItemDto.java new file mode 100644 index 0000000..cd82f36 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/dto/OrderItemDto.java @@ -0,0 +1,22 @@ +package ru.geekbrains.happy.market.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.geekbrains.happy.market.model.OrderItem; +import ru.geekbrains.happy.market.model.Product; + +@NoArgsConstructor +@Data +public class OrderItemDto { + private String productTitle; + private int quantity; + private int pricePerProduct; + private int price; + + public OrderItemDto(OrderItem orderItem) { + this.productTitle = orderItem.getProduct().getTitle(); + this.quantity = orderItem.getQuantity(); + this.pricePerProduct = orderItem.getPricePerProduct(); + this.price = orderItem.getPrice(); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/dto/ProductDto.java b/src/main/java/ru/geekbrains/happy/market/dto/ProductDto.java index 7bbd4fc..0d5f29d 100644 --- a/src/main/java/ru/geekbrains/happy/market/dto/ProductDto.java +++ b/src/main/java/ru/geekbrains/happy/market/dto/ProductDto.java @@ -1,6 +1,5 @@ package ru.geekbrains.happy.market.dto; - import lombok.Data; import lombok.NoArgsConstructor; import ru.geekbrains.happy.market.model.Product; @@ -8,7 +7,6 @@ @Data @NoArgsConstructor public class ProductDto { - private Long id; private String title; private int price; @@ -18,5 +16,4 @@ public ProductDto(Product p) { this.title = p.getTitle(); this.price = p.getPrice(); } - } diff --git a/src/main/java/ru/geekbrains/happy/market/exceptions_handling/ExceptionControllerAdvice.java b/src/main/java/ru/geekbrains/happy/market/exceptions_handling/ExceptionControllerAdvice.java new file mode 100644 index 0000000..7a646df --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/exceptions_handling/ExceptionControllerAdvice.java @@ -0,0 +1,18 @@ +package ru.geekbrains.happy.market.exceptions_handling; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +@Slf4j +public class ExceptionControllerAdvice { + @ExceptionHandler + public ResponseEntity handleResourceNotFoundException(ResourceNotFoundException e) { + log.error(e.getMessage()); + MarketError err = new MarketError(HttpStatus.NOT_FOUND.value(), e.getMessage()); + return new ResponseEntity<>(err, HttpStatus.NOT_FOUND); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/exceptions_handling/MarketError.java b/src/main/java/ru/geekbrains/happy/market/exceptions_handling/MarketError.java new file mode 100644 index 0000000..68dbb3d --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/exceptions_handling/MarketError.java @@ -0,0 +1,18 @@ +package ru.geekbrains.happy.market.exceptions_handling; + +import lombok.Data; + +import java.util.Date; + +@Data +public class MarketError { + private int status; + private String message; + private Date timestamp; + + public MarketError(int status, String message) { + this.status = status; + this.message = message; + this.timestamp = new Date(); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/exceptions_handling/ResourceNotFoundException.java b/src/main/java/ru/geekbrains/happy/market/exceptions_handling/ResourceNotFoundException.java new file mode 100644 index 0000000..eddf8c9 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/exceptions_handling/ResourceNotFoundException.java @@ -0,0 +1,7 @@ +package ru.geekbrains.happy.market.exceptions_handling; + +public class ResourceNotFoundException extends RuntimeException { + public ResourceNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/model/OrderItem.java b/src/main/java/ru/geekbrains/happy/market/model/OrderItem.java new file mode 100644 index 0000000..34c31ff --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/model/OrderItem.java @@ -0,0 +1,47 @@ +package ru.geekbrains.happy.market.model; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@NoArgsConstructor +@Data +@Entity +@Table(name = "order_items") +public class OrderItem { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @ManyToOne + @JoinColumn(name = "product_id") + private Product product; + + @Column(name = "quantity") + private int quantity; + + @Column(name = "price_per_product") + private int pricePerProduct; + + @Column(name = "price") + private int price; + + public OrderItem(Product product) { + this.product = product; + this.quantity = 1; + this.pricePerProduct = product.getPrice(); + this.price = this.pricePerProduct; + } + + public void incrementQuantity() { + quantity++; + price = quantity * pricePerProduct; + } + + public void decrementQuantity() { + quantity--; + price = quantity * pricePerProduct; + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/model/Product.java b/src/main/java/ru/geekbrains/happy/market/model/Product.java index 3618724..30b0cf7 100644 --- a/src/main/java/ru/geekbrains/happy/market/model/Product.java +++ b/src/main/java/ru/geekbrains/happy/market/model/Product.java @@ -26,11 +26,9 @@ public class Product { @Column(name = "created_at") @CreationTimestamp - private LocalDateTime createAt; + private LocalDateTime createdAt; @Column(name = "updated_at") @UpdateTimestamp - private LocalDateTime updateAt; - - + private LocalDateTime updatedAt; } diff --git a/src/main/java/ru/geekbrains/happy/market/model/Role.java b/src/main/java/ru/geekbrains/happy/market/model/Role.java new file mode 100644 index 0000000..0bbc61a --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/model/Role.java @@ -0,0 +1,29 @@ +package ru.geekbrains.happy.market.model; + +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Data +@Table(name = "roles") +public class Role { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "name") + private String name; + + @CreationTimestamp + @Column(name = "created_at") + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/src/main/java/ru/geekbrains/happy/market/model/User.java b/src/main/java/ru/geekbrains/happy/market/model/User.java new file mode 100644 index 0000000..c0259b3 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/model/User.java @@ -0,0 +1,42 @@ +package ru.geekbrains.happy.market.model; + +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import javax.persistence.*; +import java.time.LocalDateTime; +import java.util.Collection; + +@Entity +@Data +@Table(name = "users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "username") + private String username; + + @Column(name = "password") + private String password; + + @Column(name = "email") + private String email; + + @ManyToMany + @JoinTable(name = "users_roles", + joinColumns = @JoinColumn(name = "user_id"), + inverseJoinColumns = @JoinColumn(name = "role_id")) + private Collection roles; + + @CreationTimestamp + @Column(name = "created_at") + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} \ No newline at end of file diff --git a/src/main/java/ru/geekbrains/happy/market/repositories/ProductRepository.java b/src/main/java/ru/geekbrains/happy/market/repositories/ProductRepository.java index ba8173a..fbf9957 100644 --- a/src/main/java/ru/geekbrains/happy/market/repositories/ProductRepository.java +++ b/src/main/java/ru/geekbrains/happy/market/repositories/ProductRepository.java @@ -1,12 +1,12 @@ package ru.geekbrains.happy.market.repositories; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; import ru.geekbrains.happy.market.model.Product; import java.util.List; @Repository -public interface ProductRepository extends JpaRepository { - List findAllByPriceBetween(int min, int max); +public interface ProductRepository extends JpaRepository, JpaSpecificationExecutor { } diff --git a/src/main/java/ru/geekbrains/happy/market/repositories/UserRepository.java b/src/main/java/ru/geekbrains/happy/market/repositories/UserRepository.java new file mode 100644 index 0000000..2791c47 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/repositories/UserRepository.java @@ -0,0 +1,12 @@ +package ru.geekbrains.happy.market.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.geekbrains.happy.market.model.User; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + Optional findByUsername(String username); +} diff --git a/src/main/java/ru/geekbrains/happy/market/repositories/specifications/ProductSpecifications.java b/src/main/java/ru/geekbrains/happy/market/repositories/specifications/ProductSpecifications.java new file mode 100644 index 0000000..f18f965 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/repositories/specifications/ProductSpecifications.java @@ -0,0 +1,40 @@ +package ru.geekbrains.happy.market.repositories.specifications; + +import org.springframework.data.jpa.domain.Specification; +import org.springframework.util.MultiValueMap; +import ru.geekbrains.happy.market.model.Product; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import java.math.BigDecimal; +import java.util.Optional; + +public class ProductSpecifications { + private static Specification priceGreaterOrEqualsThan(int minPrice) { + return (Specification) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo(root.get("price"), minPrice); + } + + private static Specification priceLesserOrEqualsThan(int maxPrice) { + return (Specification) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.lessThanOrEqualTo(root.get("price"), maxPrice); + } + + private static Specification titleLike(String titlePart) { + return (Specification) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.like(root.get("title"), String.format("%%%s%%", titlePart)); + } + + public static Specification build(MultiValueMap params) { + Specification spec = Specification.where(null); + if (params.containsKey("min_price") && !params.getFirst("min_price").isBlank()) { + spec = spec.and(ProductSpecifications.priceGreaterOrEqualsThan(Integer.parseInt(params.getFirst("min_price")))); + } + if (params.containsKey("max_price") && !params.getFirst("max_price").isBlank()) { + spec = spec.and(ProductSpecifications.priceLesserOrEqualsThan(Integer.parseInt(params.getFirst("max_price")))); + } + if (params.containsKey("title") && !params.getFirst("title").isBlank()) { + spec = spec.and(ProductSpecifications.titleLike(params.getFirst("title"))); + } + return spec; + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/services/ProductService.java b/src/main/java/ru/geekbrains/happy/market/services/ProductService.java index 4018ede..38269fe 100644 --- a/src/main/java/ru/geekbrains/happy/market/services/ProductService.java +++ b/src/main/java/ru/geekbrains/happy/market/services/ProductService.java @@ -4,6 +4,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import ru.geekbrains.happy.market.dto.ProductDto; import ru.geekbrains.happy.market.model.Product; @@ -18,38 +19,22 @@ public class ProductService { private final ProductRepository productRepository; - - // + - public Optional findProductById(Long id) { - return productRepository.findById(id).stream().map(ProductDto::new).findFirst(); - } -// public Optional findProductById(Long id) { -// return productRepository.findById(id); -// } - - public List findAll() { - return productRepository.findAll().stream().map(ProductDto::new).collect(Collectors.toList()); + public Optional findProductById(Long id) { + return productRepository.findById(id); } - public Page findAll(int page) { - Page originalPage = productRepository.findAll(PageRequest.of(page - 1, 5)); - return new PageImpl<>(originalPage.getContent().stream().map(ProductDto::new).collect(Collectors.toList()), originalPage.getPageable(), originalPage.getTotalElements()); + public Optional findProductDtoById(Long id) { + return productRepository.findById(id).map(ProductDto::new); } - // + - public List findAllByPrice(int min, int max) { - return productRepository.findAllByPriceBetween(min, max).stream().map(ProductDto::new).collect(Collectors.toList()); + public Page findAll(Specification spec, int page, int pageSize) { + return productRepository.findAll(spec, PageRequest.of(page - 1, pageSize)).map(ProductDto::new); } -// public List findAllByPrice(int min, int max) { -// return productRepository.findAllByPriceBetween(min, max); -// } - // - public Product saveOrUpdate(Product product) { return productRepository.save(product); } - // - public void deleteById(Long id) { productRepository.deleteById(id); } diff --git a/src/main/java/ru/geekbrains/happy/market/services/UserService.java b/src/main/java/ru/geekbrains/happy/market/services/UserService.java new file mode 100644 index 0000000..789132d --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/services/UserService.java @@ -0,0 +1,38 @@ +package ru.geekbrains.happy.market.services; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.geekbrains.happy.market.model.Role; +import ru.geekbrains.happy.market.model.User; +import ru.geekbrains.happy.market.repositories.UserRepository; + +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class UserService implements UserDetailsService { + private final UserRepository userRepository; + + public Optional findByUsername(String username) { + return userRepository.findByUsername(username); + } + + @Override + @Transactional + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = findByUsername(username).orElseThrow(() -> new UsernameNotFoundException(String.format("User '%s' not found", username))); + return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), mapRolesToAuthorities(user.getRoles())); + } + + private Collection mapRolesToAuthorities(Collection roles) { + return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/resources/db/migration/V1__init.sql b/src/main/resources/db/migration/V1__init.sql index 76c6926..7703cf7 100644 --- a/src/main/resources/db/migration/V1__init.sql +++ b/src/main/resources/db/migration/V1__init.sql @@ -1,17 +1,72 @@ +create table users ( + id bigserial primary key, + username varchar(30) not null unique, + password varchar(80) not null, + email varchar(50) unique, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp +); + +create table roles ( + id bigserial primary key, + name varchar(50) not null unique, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp +); + +CREATE TABLE users_roles ( + user_id bigint not null references users (id), + role_id bigint not null references roles (id), + primary key (user_id, role_id) +); + +insert into roles (name) +values +('ROLE_USER'), +('ROLE_ADMIN'); + +insert into users (username, password, email) +values +('bob', '$2a$04$Fx/SX9.BAvtPlMyIIqqFx.hLY2Xp8nnhpzvEEVINvVpwIPbA3v/.i', 'bob_johnson@gmail.com'), +('john', '$2a$04$Fx/SX9.BAvtPlMyIIqqFx.hLY2Xp8nnhpzvEEVINvVpwIPbA3v/.i', 'john_johnson@gmail.com'); + +insert into users_roles (user_id, role_id) +values +(1, 1), +(2, 2); + create table products ( - id bigserial primary key, - title varchar(255), - price int, - created_at timestamp default current_timestamp, - updated_at timestamp default current_timestamp - ); -insert into products (title, price) values -('Bread', 10), -('Milk', 27), -('Cheese1', 94), -('Cheese2', 974), -('Cheese3', 964), -('Cheese4', 954), -('Cheese5', 94), -('Cheese6', 2924), -('Cheese7', 394); \ No newline at end of file + id bigserial primary key, + title varchar(255), + price int, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp +); + +insert into products (title, price) +values +('Bread', 24), +('Milk', 65), +('Cheese', 320), +('Cheese2', 322), +('Cheese3', 323), +('Cheese4', 324), +('Cheese5', 325), +('Cheese6', 326), +('Cheese7', 327), +('Cheese8', 328), +('Cheese9', 328), +('Cheese10', 328), +('Cheese11', 328), +('Cheese12', 328), +('Cheese13', 328), +('Cheese14', 328), +('Cheese15', 328); + +create table order_items ( + id bigserial primary key, + title varchar(255), + quantity int, + price_per_item int, + price int +); \ No newline at end of file diff --git a/src/main/resources/secured.properties b/src/main/resources/secured.properties new file mode 100644 index 0000000..e816be0 --- /dev/null +++ b/src/main/resources/secured.properties @@ -0,0 +1 @@ +jwt.secret=ifh9v8eyf9843hf3498hg934gft093whf09hG&@G*&Gd983wgf398gf973g837fg \ No newline at end of file diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 98a0879..f189566 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -13,89 +13,112 @@ -

Happy Market

- - - -

Фильтр

-
- -
- -
- - +
+ +

Пожалуйста, введите имя пользователья и пароль

+ + + + + +

© GeekBrains 2021

+ +
+ +
+
+

Вы вошли как {{user.username}}

+
+

Фильтр

+
+
+
+ + +
+
+ + +
+
+ + +
- -
- - +
+
- -
- -
- -
-
- - - -

Список товаров

- - - - - - - - - - - - - - - - - - -
НазваниеЦена
{{p.title}}{{p.price}} - -
- - - - - - - -

Добавить новый товар

-
- -
- - -
- -
- - -
- -
- -
- -
+ + +

Список товаров

+ + + + + + + + + + + + + + + + + +
НазваниеЦена
{{p.title}}{{p.price}} + + + +
+ + + +

Корзина

+ + + + + + + + + + + + + + + + + + + + +
НазваниеКоличествоЦена
{{oi.productTitle}}{{oi.quantity}}{{oi.quantity + ' x ' + oi.pricePerProduct + ' = ' + oi.price}}
Итого{{Cart.totalPrice}}
+ +
\ No newline at end of file diff --git a/src/main/resources/static/index.js b/src/main/resources/static/index.js index b11e637..52b798c 100644 --- a/src/main/resources/static/index.js +++ b/src/main/resources/static/index.js @@ -1,10 +1,10 @@ angular.module('app', []).controller('indexController', function ($scope, $http) { - const contextPath = 'http://localhost:8189/happy/api/v1'; - + const contextPath = 'http://localhost:8189/happy'; + $scope.authorized = false; $scope.fillTable = function (pageIndex = 1) { $http({ - url: contextPath + '/products', + url: contextPath + '/api/v1/products', method: 'GET', params: { title: $scope.filter ? $scope.filter.title : null, @@ -14,12 +14,31 @@ angular.module('app', []).controller('indexController', function ($scope, $http) } }).then(function (response) { $scope.ProductsPage = response.data; - $scope.PaginationArray = $scope.generatePageIndexes(1, $scope.ProductsPage.totalPages); + + let minPageIndex = pageIndex - 2; + if (minPageIndex < 1) { + minPageIndex = 1; + } + + let maxPageIndex = pageIndex + 2; + if (maxPageIndex > $scope.ProductsPage.totalPages) { + maxPageIndex = $scope.ProductsPage.totalPages; + } + + $scope.PaginationArray = $scope.generatePagesIndexes(minPageIndex, maxPageIndex); }); }; + $scope.showCart = function () { + $http({ + url: contextPath + '/api/v1/cart', + method: 'GET' + }).then(function (response) { + $scope.Cart = response.data; + }); + }; - $scope.generatePageIndexes = function (startPage, endPage) { + $scope.generatePagesIndexes = function(startPage, endPage) { let arr = []; for (let i = startPage; i < endPage + 1; i++) { arr.push(i); @@ -27,25 +46,55 @@ angular.module('app', []).controller('indexController', function ($scope, $http) return arr; } + // $scope.submitCreateNewProduct = function () { + // $http.post(contextPath + '/products', $scope.newProduct) + // .then(function (response) { + // $scope.newProduct = null; + // $scope.fillTable(); + // }); + // }; - $scope.submitCreateNewProduct = function () { - $http.post(contextPath + '/products', $scope.newProduct) + $scope.deleteProductById = function (productId) { + $http.delete(contextPath + '/api/v1/products/' + productId) .then(function (response) { - $scope.newProduct = null; $scope.fillTable(); }); - }; - - - $scope.deleteProductById = function (productId) { - $http.delete(contextPath + '/products/' + productId).then(function (response) { - $scope.fillTable(); - }) } + $scope.addToCart = function (productId) { + $http.get(contextPath + '/api/v1/cart/add/' + productId) + .then(function (response) { + $scope.showCart(); + }); + } - $scope.fillTable() + $scope.clearCart = function () { + $http.get(contextPath + '/api/v1/cart/clear') + .then(function (response) { + $scope.showCart(); + }); + } -}); + $scope.tryToAuth = function () { + $http.post(contextPath + '/auth', $scope.user) + .then(function successCallback(response) { + if (response.data.token) { + $http.defaults.headers.common.Authorization = 'Bearer ' + response.data.token; + // $scope.user.username = null; + $scope.user.password = null; + $scope.authorized = true; + $scope.fillTable(); + $scope.getUsername(); + } + }, function errorCallback(response) { + window.alert("Error"); + }); + }; + $scope.getUsername = function () { + return $scope.Authorization.getUsername(); + }; + // $scope.fillTable(); + // $scope.showCart(); +}); \ No newline at end of file