Страницы

Поиск по вопросам

Показаны сообщения с ярлыком spring-jpa. Показать все сообщения
Показаны сообщения с ярлыком spring-jpa. Показать все сообщения

понедельник, 2 марта 2020 г.

Правильный тип для String int double boolean

#java #sql #база_данных #spring_boot #spring_jpa


Задача стоит в проектировании БД для интернет магазина. 

У каждого товара есть набор характеристик (связь многие ко многим разбивается через
промежуточную табличку).
У сущности "характеристика" есть кроме id еще String name и ??? value

Вопрос в том, какой лучше тип выбрать для value ?? пока склоняюсь к типу String,
в который можно писать все вышеперечисленные типы.
Еще есть мысли по поводу не заморачиваться и написать туда Object или Serializible. 

С точки зрения проектирования какое решение будет правильнее?

Spring Boot (Spring JPA)
    


Ответы

Ответ 1



Еще есть мысли по поводу не заморачиваться и написать туда Object или Serializible. А заморочиться придется... Это ровно та задача, которая стоит перед любым разработчиком универсальной структуры данных. Путь известен и проторен не одним десятком прогеров, включая и меня самого. Основные положения: Типизация (без него никуда), с основными типами: целое, с плавающей точкой, булевая, дата, строка. Ну может еще что-то. Реализация типа на уровне класса Если подробнее то примерно так (простейшая реализация для двух типов строка и целое число): abstract class MyType { abstract void setValue(int value); abstract void setValue(String value); abstract String getValue(); } class MyTypeString extends MyType { private String valueString=null; @Override void setValue(int value) { valueString=new StringBuilder().append(value).toString(); } @Override void setValue(String value) { valueString=value; } @Override String getValue() { return value; } } class MyTypeInt extends MyType { private Integer valueInt=null; @Override void setValue(int value) { valueInt=value; } @Override void setValue(String value) { try { int val=Integer.parseInt(value); valueInt=new Integer(val); } catch(Exception ex) { valueInt=null; } } @Override String getValue() { return new StringBuilder().append(valueInt).toString(); } }

Ответ 2



Выбрать "правильнее" не получится, зависит от реализации и необходимых задач. Как вариант, добавить таблицу справочник характеристик, в которой хранить название характеристики и её тип (например Ватт, число), а в самой характеристике хранить переменную с типом Object. В таком случае, когда нам необходимо добавить значение на экран, можно просто воспользоваться .toString(), когда нам необходимо сделать фильтрацию (например, показать для товара ползунок с максимальным и минимальным значением (блок питания 300-1000 Ватт)) используем справочник характеристик и конвертируем значение в нужный формат.

Ответ 3



Я сталкивался с 2 вариантами реализации подобных задач. В обоих случаях требуется завести справочник характеристик с информацией о типе значения (число, строка, дата и пр.). Вариант 1 Для каждого типа значений создать отдельную таблицу number_values(товар, характеристика, значение-число), string_values(товар, характеристика, значение-строка) и т.д. Плюсы: Контроль данных на уровне БД (например, для числовой характеристики в number_values не получится записать строку и пр. мусор). Расширяемость - при добавлении нового типа данных не требуется изменять существующие сущности (нужно только создать таблицу для значений нового типа и добавить условие в общий SQL-запрос (см. далее)). Минусы: Сложный общий SQL-запрос для выборки значений характеристик - нужно делать join-ы с определенной таблицей со значениями в зависимости от типа характеристики. Усложнение логики вставки/обновления/удаления значения характеристики - таблица также определяется в зависимости от типа характеристики (НО, учитывая, что структура таблиц со значениями одинакова, отличается только тип значения, то можно легко написать общий код). Нужно самостоятельно контролировать консистентность данных на предмет того, чтобы, например, по одной и той же характеристике одного товара не было записей в нескольких таблице значений разных типов. Вариант 2 - использовать одну разряженную таблицу values(товар, характеристика, значение-число, значение-строка, значение-дата, ...), где заполнено только одно из значений. Плюсы: Значения атрибутов выбираются из одной таблицы -> простой SQL-запрос Для каждого товара не более одного значения любой характеристики Минусы: Сложность в расширении - необходимо модифицировать общую таблицу со значениями Хранение в одной большой таблице при большом объеме данных станет узким местом В обоих случаях я бы советовал вам в результатах выборки запроса возвращать типизированные значения, а уже на уровне бизнес-логики проводить определенные манипуляции.

суббота, 11 января 2020 г.

Как работает метод flush() в spring-data-jpa?

#java #spring_boot #spring_jpa #transaction #flush


Есть проект на стеке spring-boot-2.0.3, spring-data-jpa-2.0.3 и postgresql-9.5.13.
Всего три класса: @Entity, @Repository и основной класс. В БД одна таблица с двумя
колонками: id и name.

Добавляю в эту таблицу 100 000 записей в цикле и замеряю время исполнения запроса,
включая или отключая метод flush() класса EntityManager через каждые 100 записей.

Ожидаемый результат: при включенном методе flush() время выполнения запроса должно
быть значительно меньше, чем при выключенном.

Фактический результат: противоположные показания.



Вопрос: Что я делаю не так?



Структура проекта:





application.properties

spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/twofold
spring.datasource.username=postgres
spring.datasource.password=postgres




User.java

package twofold.data;

import javax.persistence.*;

@Entity
@Table(name = "users", schema = "public")
public class User {

    private Long id;
    private String name;

    public User() {}

    public User(String name) {
        this.name = name;
    }

    @Id
    @SequenceGenerator(name = "users_id_seq", sequenceName = "users_id_seq", allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "users_id_seq")
    @Column(name = "id", nullable = false)
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Column(name = "name", nullable = false, length = 50)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "id: " + id + "; name: " + name + ";";
    }
}




UserRepository.java

package twofold.data;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository {
}




Application.java

package twofold;

import twofold.data.User;
import twofold.data.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Autowired
    private UserRepository userRepository;

    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public CommandLineRunner addUsers() {
        return new CommandLineRunner() {
            @Transactional
            public void run(String... args) throws Exception {
                long incoming = System.currentTimeMillis();
                for (int i = 1; i <= 100000; i++) {
                    userRepository.save(new User(i + "_name"));
                    if (i % 100 == 0) {
                        entityManager.flush();
                        entityManager.clear();
                    }
                }
                System.out.println("Time: " + (System.currentTimeMillis()-incoming));
            }
        };
    }
}

    


Ответы

Ответ 1



Если в двух словах, то метод flush() производит запись сохраненных данных непосредственно в СУБД. При нормальном исполнении (без flush) у вас данные в БД попадают не сразу, а после того, как завершится транзакция (при настройках по умолчанию). То есть ваш код за эти 100 000 итераций порождает около 1000 походов в БД для сохранения данных, что увеличивает время выполнения метода.

понедельник, 20 мая 2019 г.

Правильный тип для String int double boolean

Задача стоит в проектировании БД для интернет магазина.
У каждого товара есть набор характеристик (связь многие ко многим разбивается через промежуточную табличку). У сущности "характеристика" есть кроме id еще String name и ??? value
Вопрос в том, какой лучше тип выбрать для value ?? пока склоняюсь к типу String, в который можно писать все вышеперечисленные типы. Еще есть мысли по поводу не заморачиваться и написать туда Object или Serializible.
С точки зрения проектирования какое решение будет правильнее?
Spring Boot (Spring JPA)


Ответ

Еще есть мысли по поводу не заморачиваться и написать туда Object или Serializible.
А заморочиться придется...
Это ровно та задача, которая стоит перед любым разработчиком универсальной структуры данных. Путь известен и проторен не одним десятком прогеров, включая и меня самого. Основные положения:
Типизация (без него никуда), с основными типами: целое, с плавающей точкой, булевая, дата, строка. Ну может еще что-то. Реализация типа на уровне класса
Если подробнее то примерно так (простейшая реализация для двух типов строка и целое число):
abstract class MyType { abstract void setValue(int value); abstract void setValue(String value); abstract String getValue(); } class MyTypeString extends MyType { private String valueString=null;
@Override void setValue(int value) { valueString=new StringBuilder().append(value).toString(); }
@Override void setValue(String value) { valueString=value; }
@Override String getValue() { return value; } }
class MyTypeInt extends MyType { private Integer valueInt=null;
@Override void setValue(int value) { valueInt=value; }
@Override void setValue(String value) { try { int val=Integer.parseInt(value); valueInt=new Integer(val); } catch(Exception ex) { valueInt=null; } }
@Override String getValue() { return new StringBuilder().append(valueInt).toString(); } }