Veröffentlicht aminDevAufrufe: Symbole im Artikel gezählt: 4.9k WörterLesezeit ≈6 min
This is the third post in a series of posts to cover different aspects of Spring Boot. Please note that the entire post isn’t necessarily only written in English.
In this post, I am going to cover the basics of Spring Data, including JDBC (Java DataBase Connectivity) & JPA (Java Persistence API).
Debug
By using JPA instead of JDBC, data.sql is not executed & schema.sql is not required.
If schema.sql & data.sql are under main/resources, then either null or filled with SQL statements.
Commenting out the SQL lines will cause problems.
If schema.sql exists under main/resources, JPA will execute DDL based on schema.sql; Otherwise, JPA auto creates the table based on the package domain.
org.hibernate.exception.SQLGrammarException: could not extract ResultSet
When use H2 in-memory DB, we can use @ManytoMany.
But with an external MySQL DB, we always need a 3rd table, therefore the annotation is split to:
@OnetoMany
@ManytoOne
Exceptions:
1 2 3 4 5 6 7
// 第一种: Hibernate Exception org.hibernate.InstantiationException: No default constructor for entity: : demo.taco.domain.Ingredient;
// 第二种: ERROR 12470 --- [restartedMain] o.s.boot.SpringApplication: Application run failed java.lang.IllegalStateException: Failed to execute CommandLineRunner
Must add following annotations to domain.Ingredient:
1 2 3
// Needs a constructor with no arguments, and other constructors with args @RequiredArgsConstructor @NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
Maven install failed:
1 2
Failed to execute goal org.apache.maven.plugins: maven-surefire-plugin:2.12:test (default-test) on project.
<!-- Spring Boot 2.2.6 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.19.1</version> </plugin>
JPA error with Entity Manager Factory:
1 2 3 4
[JPA] org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource[]: Invocation of init method failed; Unable to build Hibernate SessionFactory;
@Override // find one ingredient public Ingredient findOne(String id){ Connection connection = null; PreparedStatement statement = null; ResultSet rs = null; try { connection = dataSource.getConnection(); statement = connection.prepareStatement ( // SQL query is 👇 "select id, name, type from Ingredient where id = ?"); statement.setString(1, id); rs = statement.executeQuery(); Ingredient ingredient = null; if (rs.next()) ingredient = new Ingredient(rs.getString("id"), rs.getString("name"), Ingredient.Type.valueOf(rs.getString("type"))); return ingredient; } catch (SQLException e) { /* some exceptions */ } finally { if (rs != null) { try { rs.close(); } catch (SQLException e) {} } if (statement != null) { try { statement.close(); } catch (SQLException e) {} } if (connection != null) { try { statement.close(); } catch (SQLException e) {} } } returnnull; }
Sample query with JdbcTemplate:
No statements / connections being created & cleaned up. No try & catch
Focus on performing the query & mapping results to an Ingredient object
1 2 3 4 5 6 7 8 9 10 11 12
private JdbcTemplate jdbc;
@Override public Ingredient findOne(String id){ return jdbc.queryForObject ( "select id, name, type from Ingredient where id=?", this::mapRowToIngredient, id); }
IngredientRepositoryimplementation: (use JdbcTemplate to query the database)
Create a constructor:
The constructor assigns JdbcTemplate to an instance variable that will be used in other methods to query & insert into the DB.
1 2 3 4 5 6
@Autowired
// Injects the RepoImpl with JdbcTemplate, via the @Autowired annotated constructor publicIngredientRepositoryImpl(JdbcTemplate jdbc){ this.jdbc = jdbc; }
// update() doesn't give us the auto generated tacoID // so we need saveTacoInfo to get the ID privatevoidsaveIngredientToTaco(Ingredient ingredient, long tacoId){
@Override // save() doesn't save anything, but define the flow for saing an order & associated taco object 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; }
@Id @GeneratedValue(strategy=Generationtype.AUTO) private Long id; @NotNull @Size(min=5, message="Name must be at least 5 characters long") private String name;
@ManyToMany(targetEntity=Ingredient.class) @Size(min=1, message="You must choose at least 1 ingredient") private List<String> ingredients; private Data createdAt; // set createdAt to the current time before Taco is persisted @PrePersist voidcreatedAt(){ this.createdAt = new Date(); } }
When generating the repository implementation, Spring Data parse the method name in the Repository, and attempts to understand the method’s purpose in the context of the persisted object. Spring Data defines domain-specific language (DSL) , where persistence details are expressed in repository method signatures.
Repository methods are composed of:
verb (e.g. find = get = read, count)
subject (optional)
By
predicate (e.g. DeliveryZip)
operator (条件 / 比较)
Get all orders delivered to a given zip:
1 2 3
// Find all Order entities by matching deliveryZip with the param passed in the interface // String deliveryZip is the passed parameter List<Order> findByDeliveryZip(String deliveryZip);
Get all orders delivered to a given zip within a time range:
1 2 3 4
// You can write: getPuppies, and it still works. Because subject is optional // start & end date must fall between the given values List<Order> getOrdersByDeliveryZipAndPlacedAtBetween( String deliveryZip, Date startDate, Date endDate);
@GetMapping("/current") public String orderForm(){ return"orderForm"; }
@PostMapping public String processOrder(@Valid Order order, Errors errors, SessionStatus sessionStatus){ if (errors.hasErrors()) return"orderForm"; // save() method, save to repo orderRepo.save(order); // set to complete sessionStatus.setComplete();