springboot学习14

一、使用 Spring JdbcTemplate:
首先使用 Spring 对 JDBC(Java Database Connectivity)的支持来消除样板代码。然后,将重新使用 JPA(Java Persistence API)处理数据存储库,从而消除更多代码。

Spring JDBC 支持起源于 JdbcTemplate 类。JdbcTemplate 提供了一种方法,通过这种方法,可以对关系数据库执行 SQL 操作,与通常使用 JDBC 不同的是,这里不需要满足所有的条件和样板代码。

在没有 JdbcTemplate 的情况下用 Java 执行一个简单的查询:

@Override
public Ingredient findOne(String id) {
    
    
    Connection connection = null;
    PreparedStatement statement = null;
    ResultSet resultSet = null;
    try {
    
    
        connection = dataSource.getConnection();
        statement = connection.prepareStatement(
            "select id, name, type from Ingredient");
        statement.setString(1, id);
        resultSet = statement.executeQuery();
        Ingredient ingredient = null;
        if(resultSet.next()) {
    
    
            ingredient = new Ingredient(
                resultSet.getString("id"),
                resultSet.getString("name"),
                Ingredient.Type.valueOf(resultSet.getString("type")));
        }
        return ingredient;
    } catch (SQLException e) {
    
    
        // ??? What should be done here ???
    } finally {
    
    
        if (resultSet != null) {
    
    
            try {
    
    
                resultSet.close();
            } catch (SQLException e) {
    
    
            }
        }
        if (statement != null) {
    
    
            try {
    
    
                statement.close();
            } catch (SQLException e) {
    
    
            }
        }
        if (connection != null) {
    
    
            try {
    
    
                connection.close();
            } catch (SQLException e) {
    
    
            }
        }
    }
    return null;
}
Ingredient - 保存着原料信息
Taco - 保存着关于 taco (玉米卷)设计的重要信息, 
Taco_Ingredient - 包含 Taco 表中每一行的一个或多行数据,将 Taco 映射到该 Taco 的 Ingredient
Taco_Order - 保存着重要的订单细节
Taco_Order_Tacos - 包含 Taco_Order 表中的每一行的一个或多行数据,将 Order 映射到 Order 中的Tacos

在这里插入图片描述

使用JdbcTemplate 的方法
1、引入jdbc依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

数据库如h2:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

2、定义 JDBC 存储库
IngredientRepository:

package tacos.data;import tacos.Ingredient;public interface IngredientRepository {
    
    
    
    Iterable<Ingredient> findAll();
    
    Ingredient findOne(String id);
    
    Ingredient save(Ingredient ingredient);
}

TacoRepository:

package tacos.data;import tacos.Taco;public interface TacoRepository {
    
    
    Taco save(Taco design);
}

OrderRepository:

package tacos.data;import tacos.Order;public interface OrderRepository {
    
    
    Order save(Order order);
}
使用 JdbcTemplate 保存数据的两种方法包括:
直接使用 update() 方法
使用 SimpleJdbcInsert 包装类

使用 JdbcTemplate实现JdbcIngredientRepository:

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;@Override
	public Ingredient findOne(String id) {
    
    
	    return jdbc.queryForObject(
	        "select id, name, type from Ingredient where id=?",
	        this::mapRowToIngredient, id);
	}
	
	@Override
	public Iterable<Ingredient> findAll() {
    
    
	    return jdbc.query("select id, name, type from Ingredient",
	              this::mapRowToIngredient);
	}private Ingredient mapRowToIngredient(ResultSet rs, int rowNum)
	    throws SQLException {
    
    
	    return new Ingredient(
	        rs.getString("id"),
	        rs.getString("name"),
	        Ingredient.Type.valueOf(rs.getString("type")));
	}
	
	@Override
	public Ingredient save(Ingredient ingredient) {
    
    
	//	使用 update() 方法
	    jdbc.update(
	        "insert into Ingredient (id, name, type) values (?, ?, ?)",
	        ingredient.getId(),
	        ingredient.getName(),
	        ingredient.getType().toString());
	    return ingredient;
	}
}

使用 JdbcTemplate 实现 TacoRepository:
JdbcTacoRepository:

package tacos.data;import java.sql.Timestamp;
import java.sql.Types;
import java.util.Arrays;
import java.util.Date;import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;import tacos.Ingredient;
import tacos.Taco;@Repository
public class JdbcTacoRepository implements TacoRepository {
    
    

    private JdbcTemplate jdbc;

    public JdbcTacoRepository(JdbcTemplate jdbc) {
    
    
        this.jdbc = jdbc;
    }

    @Override
    public Taco save(Taco taco) {
    
    
        long tacoId = saveTacoInfo(taco);
        taco.setId(tacoId);
        for (Ingredient ingredient : taco.getIngredients()) {
    
    
            saveIngredientToTaco(ingredient, tacoId);
        }
        return taco;
    }

// 保存 Ingredient 数据时使用的 update() 方法不能获得生成的 id,因此这里需要一个不同的 update() 方法。
// 需要的 update() 方法接受 PreparedStatementCreator 和 KeyHolder。KeyHolder 将提供生成的 Taco id,但是为了使用它,还必须创建一个 PreparedStatementCreator。
    private long saveTacoInfo(Taco taco) {
    
    
        taco.setCreatedAt(new Date());
        PreparedStatementCreator psc = new PreparedStatementCreatorFactory(
            "insert into Taco (name, createdAt) values (?, ?)",
            Types.VARCHAR, Types.TIMESTAMP
        ).newPreparedStatementCreator(
            Arrays.asList(
                taco.getName(),
                new Timestamp(taco.getCreatedAt().getTime())));

        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbc.update(psc, keyHolder);
        return keyHolder.getKey().longValue();
    }

    private void saveIngredientToTaco(Ingredient ingredient, long tacoId) {
    
    
        jdbc.update(
            "insert into Taco_Ingredients (taco, ingredient) " +"values (?, ?)",
            tacoId, ingredient.getId());
    }
}

二、使用 SimpleJdbcInsert 插入数据:
JdbcOrderRepository:

package tacos.data;import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
import com.fasterxml.jackson.databind.ObjectMapper;import tacos.Taco;
import tacos.Order;@Repository
public class JdbcOrderRepository implements OrderRepository {
    
    

    private SimpleJdbcInsert orderInserter;
    private SimpleJdbcInsert orderTacoInserter;
    private ObjectMapper objectMapper;

    @Autowired
    public JdbcOrderRepository(JdbcTemplate jdbc) {
    
    
        this.orderInserter = new SimpleJdbcInsert(jdbc)
            .withTableName("Taco_Order")
            .usingGeneratedKeyColumns("id");

        this.orderTacoInserter = new SimpleJdbcInsert(jdbc)
            .withTableName("Taco_Order_Tacos");

        this.objectMapper = new ObjectMapper();
    }
    //...
    
    @Override
	public Order save(Order order) {
    
    
	    order.setPlacedAt(new Date());
	    long orderId = saveOrderDetails(order);
	    order.setId(orderId);
	
	    List<Taco> tacos = order.getTacos();
	    for (Taco taco : tacos) {
    
    
	        saveTacoToOrder(taco, orderId);
	    }
	
	    return order;
	}
	// SimpleJdbcInsert 有两个执行插入的有用方法:execute() 和 executeAndReturnKey()。
	// 两者都接受 Map<String, Object>,其中 Map 键对应于数据插入的表中的列名,映射的值被插入到这些列中。​
	private long saveOrderDetails(Order order) {
    
    
	    // 使用 Jackson 的 ObjectMapper 及其 convertValue() 方法将 Order 转换为 Map。
	    // 这是必要的,否则 ObjectMapper 会将 Date 属性转换为 long,这与 Taco_Order 表中的 placedAt 字段不兼容
	    @SuppressWarnings("unchecked")
	    Map<String, Object> values = objectMapper.convertValue(order, Map.class);
	    values.put("placedAt", order.getPlacedAt());long orderId = orderInserter.executeAndReturnKey(values).longValue();
	
	    return orderId;
	}private void saveTacoToOrder(Taco taco, long orderId) {
    
    
	    Map<String, Object> values = new HashMap<>();
	    values.put("tacoOrder", orderId);
	    values.put("taco", taco.getId());
	
	    orderTacoInserter.execute(values);
	}
}

DesignTacoController:

@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<Ingredient> ingredients = new ArrayList<>();
        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";
    }
    
    ...
    
}

DesignTacoController:

@Controller
@RequestMapping("/design")
@SessionAttributes("order")
public class DesignTacoController {
    
    
    private final IngredientRepository ingredientRepo;
    private TacoRepository designRepo;

    @Autowired
    public DesignTacoController(
        IngredientRepository ingredientRepo,
        TacoRepository designRepo) {
    
    
        this.ingredientRepo = ingredientRepo;
        this.designRepo = designRepo;
    }

    @ModelAttribute(name = "order")
    public Order order() {
    
    
        return new Order();
    }

    @ModelAttribute(name = "taco")
    public Taco taco() {
    
    
        return new Taco();
    }

    @PostMapping
    public String processDesign(
        @Valid Taco design, Errors errors,
        @ModelAttribute Order order) {
    
    

        if (errors.hasErrors()) {
    
    
            return "design";
        }

        Taco saved = designRepo.save(design);
        order.addDesign(saved);

        return "redirect:/orders/current";
    }

    ...}

OrderController:

package tacos.web;import javax.validation.Valid;import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;import tacos.Order;
import tacos.data.OrderRepository;@Controller
@RequestMapping("/orders")
@SessionAttributes("order")
public class OrderController {
    
    

    private OrderRepository orderRepo;

    public OrderController(OrderRepository orderRepo) {
    
    
        this.orderRepo = orderRepo;
    }

    @GetMapping("/current")
    public String orderForm() {
    
    
        return "orderForm";
    }

    @PostMapping
    public String processOrder(@Valid Order order, Errors errors,
                               SessionStatus sessionStatus) {
    
    
        if (errors.hasErrors()) {
    
    
            return "orderForm";
        }

        orderRepo.save(order);
        // 一旦订单被保存,就不再需要它存在于 session 中了。
        // 事实上,如果不清除它,订单将保持在 session 中,包括其关联的 tacos,下一个订单将从旧订单中包含的任何 tacos 开始。
        // 因此需要 processOrder() 方法请求 SessionStatus 参数并调用其 setComplete() 方法来重置会话。
        sessionStatus.setComplete();
        return "redirect:/";
    }
}

三、使用 Spring Data 声明 JPA repositories:
使用 Spring Data JPA 持久化数据

Spring Data JPA - 针对关系数据库的持久化
Spring Data Mongo - 针对 Mongo 文档数据库的持久化
Spring Data Neo4j - 针对 Neo4j 图形数据库的持久化
Spring Data Redis - 针对 Redis 键值存储的持久化
Spring Data Cassandra - 针对 Cassandra 数据库的持久化

下面重新查看域对象并对它们进行注解以实现 JPA 持久化。
1、引入依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

如果想使用不同的 JPA 实现,那么至少需要排除 Hibernate 依赖,并包含所选择的 JPA 库。例如,要使用 EclipseLink 而不是 Hibernate,需要按如下方式更改构建:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>hibernate-entitymanager</artifactId>
            <groupId>org.hibernate</groupId>
        </exclusion>
    </exclusions>
</dependency><dependency>
    <groupId>org.eclipse.persistence</groupId>
    <artifactId>eclipselink</artifactId>
    <version>2.5.2</version>
</dependency>

2、为了将其声明为 JPA 实体,必须使用 @Entity 注解。它的 id 属性必须使用 @Id 进行注解,以便将其指定为惟一标识数据库中实体的属性。
Ingredient:

package tacos;import javax.persistence.Entity;
import javax.persistence.Id;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;@Data
@RequiredArgsConstructor
@NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
@Entity
public class Ingredient {
    
    
    @Id
    private final String id;
    private final String name;
    private final Type type;
    
    public static enum Type {
    
    
        WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
    }
}
除了特定于 JPA 的注解之外,还在类级别上添加了 @NoArgsConstructor 注解。
JPA 要求实体有一个无参构造函数,所以 Lombok 的 @NoArgsConstructor 实现了这一点。
但是要是不希望使用它,可以通过将 access 属性设置为 AccessLevel.PRIVATE 来将其设置为私有。
因为必须设置 final 属性,所以还要将 force 属性设置为 true,这将导致 Lombok 生成的构造函数将它们设置为 null。
还添加了一个 @RequiredArgsConstructor。
@Data 隐式地添加了一个必需的有参构造函数,但是当使用 @NoArgsConstructor 时,该构造函数将被删除。
显式的 @RequiredArgsConstructor 确保除了私有无参数构造函数外,仍然有一个必需有参构造函数。

把 Taco 注解为实体:
Taco:

package tacos;import java.util.Date;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.PrePersist;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import lombok.Data;@Data
@Entity
public class Taco {
    
    
    
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    
    @NotNull
    @Size(min=5, message="Name must be at least 5 characters long")
    private String name;
    
    private Date createdAt;
    
    // 要声明 Taco 及其相关 Ingredient 列表之间的关系,可以使用 @ManyToMany 注解 ingredient 属性。
    // 一个 Taco 可以有很多 Ingredient,一个 Ingredient 可以是很多 Taco 的一部分
    @ManyToMany(targetEntity=Ingredient.class)
    @Size(min=1, message="You must choose at least 1 ingredient")
    private List<Ingredient> ingredients;
    
    // @PrePersist 注解。
    // 将使用它将 createdAt 属性设置为保存 Taco 之前的当前日期和时间
    @PrePersist
    void createdAt() {
    
    
        this.createdAt = new Date();
    }
}

与 Ingredient 一样,Taco 类现在使用 @Entity 注解,其 id 属性使用 @Id 注解。因为依赖于数据库自动生成 id 值,所以还使用 @GeneratedValue 注解 id 属性,指定自动策略。

Order 注解为 JPA 实体:
Order:

package tacos;import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.PrePersist;
import javax.persistence.Table;
import javax.validation.constraints.Digits;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.CreditCardNumber;
import org.hibernate.validator.constraints.NotBlank;
import lombok.Data;@Data
@Entity
@Table(name="Taco_Order")  // 这指定订单实体应该持久化到数据库中名为 Taco_Order 的表中
public class Order implements Serializable {
    
    
    private static final long serialVersionUID = 1L;
    
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    
    private Date placedAt;
    
   // ...
    
    @ManyToMany(targetEntity=Taco.class)
    private List<Taco> tacos = new ArrayList<>();
    
    public void addDesign(Taco design) {
    
    
        this.tacos.add(design);
    }
    
    @PrePersist
    void placedAt() {
    
    
        this.placedAt = new Date();
    }
}

3、声明 JPA repository
但是使用 Spring Data,扩展 CrudRepository 接口。
IngredientRepository:

package tacos.data;import org.springframework.data.repository.CrudRepository;
import tacos.Ingredient;// CrudRepository 为 CRUD(创建、读取、更新、删除)操作声明了十几个方法。
// 注意,它是参数化的,第一个参数是存储库要持久化的实体类型,第二个参数是实体 id 属性的类型。
public interface IngredientRepository extends CrudRepository<Ingredient, String> {
    
    
}

TacoRepository:

package tacos.data;import org.springframework.data.repository.CrudRepository;
import tacos.Taco;public interface TacoRepository extends CrudRepository<Taco, Long> {
    
    
}

OrderRepository:

package tacos.data;import org.springframework.data.repository.CrudRepository;
import tacos.Order;public interface OrderRepository extends CrudRepository<Order, Long> {
    
    

	// 自定义 JPA repository:
	// 在 findByDeliveryZip() 中,动词是 find,谓词是 DeliveryZip,主语没有指定,暗示是一个 Order。
	List<Order> findByDeliveryZip(String deliveryZip);
  	
	// 需要查询在给定日期范围内投递给指定邮政编码的所有订单
	// deliveryZip 属性必须与传递给方法的第一个参数的值一致。
	// Between 关键字表示 deliveryZip 的值必须位于传入方法最后两个参数的值之间.
  	List<Order> readOrdersByDeliveryZipAndPlacedAtBetween(
    String deliveryZip, Date startDate, Date endDate);
	
	// AllIgnoringCase 或 AllIgnoresCase 来忽略所有 String 比较的大小写。
	List<Order> findByDeliveryToAndDeliveryCityAllIgnoresCase(
    String deliveryTo, String deliveryCity);
    
    // 将 OrderBy 放在方法名的末尾,以便根据指定的列对结果进行排序。
    List<Order> findByDeliveryCityOrderByDeliveryTo(String city);
    
    // 可以随意将方法命名为任何想要的名称,
    // 并使用 @Query 对其进行注解,以显式地指定调用方法时要执行的查询
    @Query("Order o where o.deliveryCity='Xxx'")
	List<Order> readOrdersDeliveredInXxx();
}

4、Spring Data 方法签名还包括:

IsAfter After IsGreaterThan GreaterThan
IsGreaterThanEqual GreaterThanEqual
IsBefore Before IsLessThan LessThan
IsLessThanEqual LessThanEqual
IsBetween Between
IsNull Null
IsNotNull NotNull
IsIn In
IsNotIn NotIn
IsStartingWith StartingWith StartsWith
IsEndingWith EndingWith EndsWith
IsContaining Containing Contains
IsLike Like
IsNotLike NotLike
IsTrue True
IsFalse False
Is Equals
IsNot Not
IgnoringCase IgnoresCase
当应用程序启动时,Spring Data JPA 会动态地自动生成一个实现。
这意味着 repository 可以从一开始就使用。
只需将它们注入到控制器中,就像在基于 JDBC 的实现中所做的那样。
CrudRepository 提供的基本 CRUD 操作之外。
Spring Data 还将 find、read 和 get 理解为获取一个或多个实体的同义词。
repository 的方法由一个动词、一个可选的主语、单词 by 和一个谓词组成。

猜你喜欢

转载自blog.csdn.net/tongwudi5093/article/details/113786948