简体   繁体   English

Java JDBC 模板等效于 EntityFramework Include,map 将列表加入子类

[英]Java JDBC Template Equivalent for EntityFramework Include, map join list into subclass

Does Java JDBC Template have the equivalent of Entity Framework Linq Include? Java JDBC 模板是否具有相当于实体框架 Linq 的内容? Whats the best way to conduct this if not in the JDBCTemplate library?如果不在 JDBCTemplate 库中,最好的方法是什么?

For example in.Net, the following example would map a single-to-many database relationship into Product Class, with Suppliers and Locations below automatically.例如在.Net 中,以下示例将 map 单对多数据库关系转换为 Product Class,并自动在下方显示供应商和位置。

SQL SQL

select * from dbo.Product
inner join dbo.Suppliers 
   on suppliers.productId = product.productId
inner join dbo.Locations 
   on locations.productId = product.productId

Target Class:目标 Class:

public class Product{
    private Long productId;
    private String productName;
    private List<Supplier> suppliers; 
    private List<Location> locations;   
}

C# C#

List<Product> productList = dbContext.Product.Include(x=>x.Supplier)
              .Include(x=>x.Location);

Java JDBC Template Proposed Solution: Java JDBC 模板建议解决方案:

How can this be done with Java and JDBC Template?如何使用 Java 和 JDBC 模板完成此操作? Is there a clean way?有干净的方法吗? I am thinking of converting the sql query into JSON for suppliers and locations, and mapping.我正在考虑将 sql 查询转换为 JSON 以获取供应商和位置以及映射。

String productQuery = 
  SELECT pr.productId
      ,pr.productName
        ,(select *
          from dbo.Supplier supp
        where supp.supplierId = pr.supplierId
      for json auto) as SuppliersJson
        ,(select *
          from dbo.Location loc
        where loc.locationId = pr.locationId
      for json auto) as LocationsJson
  FROM dbo.Product pr;

With this,有了这个,

List<Product> products = namedJdbcTemplate.queryForObject(productQuery, new ProductMapper());

@Override
public ProductMapper mapRow(ResultSet rs, int rowNum) throws SQLException {
    Product product = new Product();
    product.setProductId(rs.getLong("ProductId"));
    product.setProductName(rs.getString("ProductName"));
    product.setSuppliers(new ObjectMapper().readValue(suppliersJson, Supplier.class));
    product.setLocations(new ObjectMapper().readValue(locationsJson, Location.class));
    return product;
}

You don't need to write like that.你不需要那样写。 If you use Entity framework like eclipseLink or hibernate they are automatically mapping.如果您使用eclipseLinkhibernate等实体框架,它们会自动映射。 For example with your types:例如,您的类型:

@Entity
@Table(name = "Product")
@XmlRootElement
public class Product implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @Basic(optional = false)
    @Column(nullable = false)
    private Long productId;

    @Basic(optional = false)
    @Column(name = "PRODUCT_NAME", nullable = false, length = 200)
    private String productName;
    @OneToMany( mappedBy = "productId", fetch = FetchType.LAZY)
    private List<Supplier> suppliers;
    @OneToMany( mappedBy = "productId", fetch = FetchType.LAZY)
    private List<Location> locations;
    
    //set get

    
}

You can write your query simply like this:您可以像这样简单地编写查询:

List<Product> lst = em.createQuery("select a from Product a",Product.class).getResultList();
System.out.println(lst.getSuppliers().size());

Entity framework will automaticly fill those values but in any way I dont recommend this auto fetches for big systems for performance and ram usage.实体框架将自动填充这些值,但无论如何我不建议为大型系统自动获取性能和内存使用。

Java offers you different options to implement the desired behavior. Java 为您提供不同的选项来实现所需的行为。

First, as a naive solution, you could try using JDBC directly and iterate over the different results returned by your query, taking into consideration the last fetched result of every type, and build the corresponding object graph on your own.首先,作为一个简单的解决方案,您可以尝试直接使用 JDBC 并遍历查询返回的不同结果,同时考虑每种类型的最后获取结果,并自行构建相应的 object 图。

For instance, given the following query (note that it is ordered):例如,给定以下查询(注意它是有序的):

select pr.*, sp.*, loc.* from dbo.Product pr
inner join dbo.Suppliers 
   on suppliers.productId = product.productId
inner join dbo.Locations 
   on locations.productId = product.productId
order by pr.productId, sp.supplierId, loc.locationId

You can use something like the following to iterate and build your object graph:您可以使用类似以下的内容来迭代和构建您的 object 图:

// You can use PreparedStatement as well if you prefer to
List<Product> products = new ArrayList<>();
Long lastProductId = null;
Product product = null;
Long lastSupplierId = null;
Supplier supplier = null;
Long lastLocationId = null;
Location location = null;
try (Statement stmt = con.createStatement()) {
     ResultSet rs = stmt.executeQuery(query);
  while (rs.next()) {
     Long productId = rs.getLong("ProductId");
     if (lastProductId == null || productId != lastProductId) {
       product = new Product();
       products.add(product);

       product.setProductId(productId);
       product.setProductName(rs.getString("ProductName"));
       product.setSuppliers(new ArrayList<>());
       product.setLocations(new ArrayList<>());

       lastProductId = productId;
       lastSupplierId = null;
       lastLocationId = null;
     }

     // Suppliers
     Long supplierId = rs.getLong("SupplierId");
     if (lastSupplierId == null || supplierId != lastSupplierId) {
       supplier = new Supplier();
       product.getSuppliers().add(supplier);

       supplier.setSupplierId(supplierId);
       //...

       lastSupplierId = supplierId;
     }

     // Locations
     Long locationId = rs.getLong("LocationId");
     if (lastLocationId == null || locationId != lastLocationId) {
       location = new Location();
       product.getLocations().add(location);

       location.setLocationId(locationId);
       //...

       lastLocationId = locationId;
     }


  }
} catch (SQLException e) {
  e.printStackTrace();
} 

With JdbcTemplate probably the way to go would be using queryForList .使用JdbcTemplate可能通往 go 的方法是使用queryForList Consider for instance:例如考虑:

// Every Map in the list is composed by the column names as keys and 
// the corresponding values as values
List<Map<String, Object>> rows = jdbcTemplate.queryForList(productQuery);

// The same code as above, but taking the information from the result Maps
List<Product> products = new ArrayList<>();
Long lastProductId = null;
Product product = null;
Long lastSupplierId = null;
Supplier supplier = null;
Long lastLocationId = null;
Location location = null;
for (Map row : rows) {
  Long productId = (Long)row.get("ProductId");
  if (lastProductId == null || productId != lastProductId) {
     product = new Product();
     products.add(product);

     product.setProductId(productId);
     product.setProductName((String)row.get("ProductName"));
     product.setSuppliers(new ArrayList<>());
     product.setLocations(new ArrayList<>());

     lastProductId = productId;
     lastSupplierId = null;
     lastLocationId = null;
   }

   // Suppliers
   Long supplierId = (Long)row.get("supplierId");
   if (lastSupplierId == null || supplierId != lastSupplierId) {
     supplier = new Supplier();
     product.getSuppliers().add(supplier);

     supplier.setSupplierId(supplierId);
     //...

     lastSupplierId = supplierId;
   }

   // Locations
   Long locationId = (Long)row.get("locationId");
   if (lastLocationId == null || locationId != lastLocationId) {
     location = new Location();
     product.getLocations().add(location);

     location.setLocationId(locationId);
     //...

     lastLocationId = locationId;
   }
} 

As you can see, both approaches are neither very extensible nor maintainable, although suitable.正如您所看到的,这两种方法都不是很好的可扩展性和可维护性,虽然都合适。

Similarly to EntityFramework, Java provides different ORM that you can use to alleviate these problems.与 EntityFramework 类似,Java 提供了不同的 ORM,您可以使用它们来缓解这些问题。

The first one is MyBatis .第一个是MyBatis

Simplifying a lot, with MyBatis you need a SqlSession , an artifact that correctly configured will allow you to obtain a reference to a Mapper .简化了很多,使用 MyBatis 你需要一个SqlSession ,正确配置的工件将允许你获得对Mapper的引用。

A Mapper is just an interface that declares different methods, one per every operation you need to perform against the database. Mapper只是一个声明不同方法的接口,每个您需要对数据库执行的操作都有一个。 A typical code block will be by like the following:典型的代码块如下所示:

try (SqlSession session = sqlSessionFactory.openSession()) {
  ProductMapper mapper = session.getMapper(ProductMapper.class);
  //...
}

Where:在哪里:

public interface ProductMapper {
  List<Product> getProducts();
}

For every Mapper you need to provide an associated XML configuration file .对于每个Mapper ,您需要提供关联的XML 配置文件 This file will define as namespace the Mapper and, typically, for every method defined in the Mapper , the appropriate configuration.该文件将Mapper定义为命名空间,并且通常为Mapper中定义的每个方法定义适当的配置。 For example:例如:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace=“my.pkg.ProducMapper”>

  <resultMap id=“productWithSuppliersAndLocationsResultMap" type=“my.pkg.Product”>
    <id property=“productId”         column=“ProductId” />
    <result property=“productName”   column=“ProductName” />
    <collection property=“suppliers” ofType=“my.pkg.Supplier”>
      <id property=“supplierId"      column=“SupplierId” />
      <!— ... —/>
    </collection>
    <collection property=“locations” ofType=“my.pkg.Location”>
      <id property=“locationId"     column=“LocationId” />
      <!— ... —/>
    </collection>
  </resultMap>

  <select id=“getProducts” resultMap="productWithSuppliersAndLocationsResultMap">
   select pr.*, sp.*, loc.* from dbo.Product pr
                 inner join dbo.Suppliers 
                     on suppliers.productId = product.productId
                 inner join dbo.Locations 
                     on locations.productId = product.productId
       order by pr.productId, sp.supplierId, loc.locationId
  </select>

</mapper>

As you can guess, it will use Reflection to populate the different properties from the provided SQL statement according to the provided resultMap .如您所料,它将使用反射根据提供的resultMap填充来自提供的 SQL 语句的不同属性。

It can be easily integrated with Spring as well.它也可以与 Spring 轻松集成

One more option, as explained as well by @utrucceh in his/her answer, is the use of JPA , which is the Java de jure ORM standard.正如@utrucceh 在他/她的回答中所解释的那样,另一种选择是使用JPA ,这是 Java法律上的 Z8B8A92C27C103F10168D764DD799DDZ 标准。

To deal with it, you first need to transform your plain objects in entities , defining their columns and the relations within them.要处理它,您首先需要在实体中转换您的普通对象,定义它们的列以及它们之间的关系。 For example:例如:

@Entity
@Table(name = "Products")
public class Product  {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long productId;

    @Column(name = “ProductName”, nullable = false)
    private String productName;

    @OneToMany(mappedBy = "product")
    private List<Supplier> suppliers;

    @OneToMany(mappedBy = "product")
    private List<Location> locations;
    
    //…

}

@Entity
@Table(name=“Suppliers”)
public class Supplier {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long supplierId;

  @ManyToOne(fetch= FetchType.LAZY)
  @JoinColumn(name=“productId”, nullable = false)
  private Product product;

  //…

}

@Entity
@Table(name=“Locations”)
public class Location {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long locationId;

  @ManyToOne(fetch= FetchType.LAZY)
  @JoinColumn(name=“productId”, nullable = false)
  private Product product;

  //…

}

This object graph can be queried in different ways.这个 object 图可以通过不同的方式进行查询。

For instance, you can use the Criteria API .例如,您可以使用标准 API

The code could be something similar to the following:代码可能类似于以下内容:

EntityManager em = ... // obtained as appropriate
CriteriaBuilder cb = em.getCriteriaBuilder();

CriteriaQuery<Product> query = cb.createQuery(Product.class);
Root<Product> root = query.from(Product.class);
query.select(root);
// Using fetchJoin will instruct the ORM to fetch, equivalent in a certain
// way to Include(), your results. The JPA implementation will do it best to
// optimize the way to return the actual information
Join<Product, Supplier> joinSuppliers = root.fetchJoin("suppliers", JoinType.INNER);
Join<Product, Location> joinLocations = root.fetchJoin("locations", JoinType.INNER);
List<Product> results = query.getResultList();

Sorry for the brevity of the examples, but both JPA and MyBatis are very extensive subjects.对不起,例子很简洁,但是 JPA 和 MyBatis 都是非常广泛的主题。

JPA implementations like Hibernate can be easily integrated with Spring. JPA 实现,如Hibernate可以很容易地与 Spring 集成。

The Spring Data JPA project provides great value added as well. Spring 数据 JPA项目也提供了巨大的附加值。

you can try JPAstreamer .你可以试试JPAstreamer it is a library for expressing JPA/Hibernate/Spring queries using standard Java streams and its documentation is quite nice.它是一个使用标准 Java 流表达 JPA/Hibernate/Spring 查询的库,它的文档非常好。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM