Вам также понадобится база данных, в которой будут храниться ваши данные. Для наших целей, встроенная база данных будет идеальна. Я предпочитаю встроенную базу данных H2, поэтому я добавил следующую зависимость в сборку:
Позже, вы увидите, как настроить приложение для использования внешней базы данных. Но сейчас, давайте перейдем к написанию репозитория, который извлекает и сохраняет данные Ingredient.
ОПРЕДЕЛЕНИЕ РЕПОЗИТОРИЕВ JDBC
Репозиторий Ingredient должен выполнять следующие операции:
Query запрос всех ингредиентов в коллекцию Ingredient объектов
Запрос одного Ingredient по его id
Сохранить объект Ingredient
Следующий интерфейс IngredientRepository определяет эти три операции объявляя методы:
package tacos.data;
import tacos.Ingredient;
public interface IngredientRepository {
Iterable
Ingredient findOne(String id);
Ingredient save(Ingredient ingredient);
}
Несмотря на то, что интерфейс отражает суть того, что вам нужно сделать в репозитории ингредиентов, вам все равно нужно написать реализацию IngredientRepository, использующую JdbcTemplate для запроса к базе данных. Код, показанный далее, является первым шагом в написании этой реализации.
листинг 3.4 -Начало репозиторий ингредиент с JdbcTemplate
package tacos.data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import tacos.Ingredient;
@Repository
public class JdbcIngredientRepository implements IngredientRepository {
private JdbcTemplate jdbc;
@Autowired
public JdbcIngredientRepository(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}
...
}
Как вы можете видеть, JdbcIngredientRepository аннотируется @Repository. Эта аннотация является одной из немногих аннотаций стереотипов, которые определяет Spring, включая @Controller и @Component. Аннотируя JdbcIngredientRepository с помощью @Repository, вы объявляете, что он должен быть автоматически обнаружен при сканировании компонентов Spring и создан как bean в контексте приложения Spring.
Когда Spring создает bean JdbcIngredientRepository, он внедряет JdbcTemplate через аннотированную конструкцию @Autowired. Конструктор назначает JdbcTemplate переменной экземпляра, которая будет использоваться в других методах для запроса и вставки в базу данных. Говоря об остальных методах, давайте взглянем на реализации findAll () и findById ().
Листинг 3.5 запросы к базе данных с JdbcTemplate
@Override
public Iterable
return jdbc.query("select id, name, type from Ingredient",
this::mapRowToIngredient);
}
@Override
public Ingredient findOne(String id) {
return jdbc.queryForObject(
"select id, name, type from Ingredient where id=?",
this::mapRowToIngredient, id);
}
private Ingredient mapRowToIngredient(ResultSet rs, int rowNum) throws QLException {
return new Ingredient(
rs.getString("id"),
rs.getString("name"),
Ingredient.Type.valueOf(rs.getString("type")));
}
findAll() и findById() используют JdbcTemplate аналогичным образом. Метод findAll(), ожидающий возврата коллекции объектов, использует JdbcTemplate метод запроса. Метод query() принимает SQL для запроса, а также реализацию Spring-овского RowMapper с целью сопоставления каждой строки в результирующем наборе объекту. findAll() также принимает в качестве окончательного аргумента (аргументов) список любых параметров, необходимых в запросе. Но, в данном случае, нет никаких обязательных параметров.
Метод findById() возвращает только один объект ингредиента, поэтому он использует метод queryForObject() JdbcTemplate вместо query(). queryForObject() работает так же, как query(), за исключением того, что возвращает один объект вместо списка объектов. В этом случае ему дается запрос для выполнения, RowMapper и id Ingredient для выборки, который используется вместо ? в запросе.
Как показано в листинге 3.5, параметр RowMapper для findAll() и findById() задается как ссылка на метод mapRowToIngredient(). Ссылки на методы Java 8 и лямбда-выражения удобны при работе с JdbcTemplate в качестве альтернативы явной реализации RowMapper. Но если по какой-то причине вы хотите или нуждаетесь в явном RowMapper, то следующая реализация findAll() показывает, как это сделать:
@Override
public Ingredient findOne(String id) {
return jdbc.queryForObject(
"select id, name, type from Ingredient where id=?",
new RowMapper
public Ingredient mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Ingredient(
rs.getString("id"),
rs.getString("name"),
Ingredient.Type.valueOf(rs.getString("type")));
};
}, id);
}
Чтение данных из базы данных-это только часть истории. В какой-то момент данные должны быть записаны в базу данных, чтобы их можно было прочитать. Итак, давайте рассмотрим реализацию метода save().
ВСТАВКА СТРОКИ
Метод update() JdbcTemplate может использоваться для любого запроса, который записывает или обновляет данные в базе данных. И, как показано в следующем листинге, его можно использовать для вставки данные в базу данных.
Листинг 3.6 вставка данных с использованием JdbcTemplate
@Override
public Ingredient save(Ingredient ingredient) {
jdbc.update(
"insert into Ingredient (id, name, type) values (?, ?, ?)",
ingredient.getId(),
ingredient.getName(),
ingredient.getType().toString());
return ingredient;
}
Поскольку нет необходимости сопоставлять данные ResultSet с объектом, метод update() намного проще, чем query() или queryForObject (). Для него требуется только String, содержащий SQL, а также значения, для параметров запроса. В данном случае запрос имеет три параметра, которые соответствуют последним трем параметрам запроса метода save() - ID ингредиента, name и type.
С JdbcIngredientRepository закончили, теперь вы можете внедрить его в DesignTacoController и использовать его для предоставления списка объектов Ingredient вместо использования жестко закодированных значений (как вы сделали в главе 2). Изменения в DesignTacoController показаны далее.
Листинг 3.7 Внедрение и использование репозитория в контроллере
@Controller
@RequestMapping("/design")
@SessionAttributes("order")
public class DesignTacoController {
private final IngredientRepository ingredientRepo;
@Autowired
public DesignTacoController(IngredientRepository ingredientRepo) {
this.ingredientRepo = ingredientRepo;
}
@GetMapping
public String showDesignForm(Model model) {
List
ingredientRepo.findAll().forEach(i -> ingredients.add(i));
Type[] types = Ingredient.Type.values();
for (Type type : types) {
model.addAttribute(type.toString().toLowerCase(),
filterByType(ingredients, type));
}
return "design";
}
...
}
Обратите внимание, что вторая строка метода showDesignForm() теперь вызывает метод findAll() внедренного IngredientRepository. Метод findAll() извлекает все ингредиенты из базы данных перед их фильтрацией в различные типы в модели.
Вы почти готовы запустить приложение и попробовать эти изменения. Но прежде чем начать чтение данных из таблицы Ingredient, которая используется в запросе, вероятно, следует создать эту таблицу и заполнить ее хоть какими-то данными ингредиентов.
3.1.3 Создание схемы и предварительная загрузка данных
Помимо таблицы Ingredient, вам также понадобятся некоторые таблицы, содержащие информацию о заказах и составе тако. На рисунке 3.1 показаны необходимые таблицы, а также связи между ними.
Рисунок 3.1 таблицы схемы Taco Cloud
Таблицы на рис. 3.1 служат следующим целям:
Ingredient - содержит информацию об ингредиенте
Taco - содержит важную информацию о составе taco
Taco_Ingredients - содержит одну или несколько строк для каждой строки в Taco, сопоставляя taco с ингредиентами для этого taco
Taco_Order-содержит важные данные order
Taco_Order_Tacos - содержит одну или несколько строк для каждой строки в Taco_Order, сопоставляя order с taco-ми в заказе
Следующий листинг показывает SQL, который создает таблицы.
Листинг 3.8 Схема Taco Cloud
create table if not exists Ingredient (
id varchar(4) not null,
name varchar(25) not null,
type varchar(10) not null
);
create table if not exists Taco (
id identity,
name varchar(50) not null,
createdAt timestamp not null
);
create table if not exists Taco_Ingredients (
taco bigint not null,
ingredient varchar(4) not null
);
alter table Taco_Ingredients add foreign key (taco) references Taco(id);