5.3 添加商品
实体类,参考基础工程:
DAO实现
@Mapper @Component public interface ProductDao { //添加商品基本信息 @Insert("insert into product_info(store_info_id,product_name,spec,region_code,price) value(#{storeInfoId},#{productName},#{spec},#{regionCode},#{price})") @Options(useGeneratedKeys = true,keyProperty = "productInfoId",keyColumn = "id") int insertProductInfo(ProductInfo productInfo); //添加商品描述信息 @Insert("insert into product_descript(product_info_id,descript,store_info_id) value(# {productInfoId},#{descript},#{storeInfoId})") @Options(useGeneratedKeys = true,keyProperty = "id",keyColumn = "id") int insertProductDescript(ProductDescript productDescript); }
service实现,针对垂直分库的两个库,分别实现店铺服务、商品服务
@Service public class ProductServiceImpl implements ProductService { @Autowired private ProductDao productDao; @Override @Transactional public void createProduct(ProductInfo product) { ProductDescript productDescript = new ProductDescript(); productDescript.setDescript(product.getDescript()); productDao.insertProductInfo(product);//新增商品基本信息 productDescript.setProductInfoId(product.getProductInfoId()); productDescript.setStoreInfoId(product.getStoreInfoId()); //冗余店铺信息 productDao.insertProductDescript(productDescript);//新增商品描述信息 } }
controller 实现:
/** * 卖家商品展示 */ @RestController public class SellerController { @Autowired private ProductService productService; @PostMapping("/products") public String createProject(@RequestBody ProductInfo productInfo) { productService.createProduct(productInfo); return "创建成功!"; }
单元测试:
@RunWith(SpringRunner.class) @SpringBootTest(classes = ShardingJdbcDemoBootstrap.class) public class ShardingTest { @Autowired ProductService productService; @Test public void testCreateProduct(){ for(long i=1;i<10;i++){ //store_info_id,product_name,spec,region_code,price,image_url ProductInfo productInfo = new ProductInfo(); productInfo.setProductName("Java编程思想"+i); productInfo.setDescript("Java编程思想是一本非常好的Java教程"+i); productInfo.setRegionCode("110000"); productInfo.setStoreInfoId(1); productInfo.setPrice(new BigDecimal(i)); productService.createProduct(productInfo); } } ...
这里使用了sharding-jdbc所提供的全局主键生成方式之一雪花算法,来生成全局业务唯一主键。
通过添加商品接口新增商品进行分库验证,store_info_id为偶数的数据在product_db_1,为奇数的数据在
product_db_2。
通过 添加商品接口新增商品进行分表验证,product_id为偶数的数据在product_info_1、product_descript_1,为奇数的数据在product_info_2、product_descript_2。
5.4 查询商品
Dao实现:
在ProductDao中定义商品查询方法:
@Select("select i.*, d.descript, r.region_name placeOfOrigin " + "from product_info i join product_descript d on i.id = d.product_info_id " + "join region r on r.region_code = i.region_code order by i.id desc limit #{start},# {pageSize}") List<ProductInfo> selectProductList(@Param("start")int start,@Param("pageSize") int pageSize);
Service实现:
在ProductServiceImpl定义商品查询方法:
@Override public List<ProductInfo> queryProduct(int page,int pageSize) { int start = (page‐1)*pageSize; return productDao.selectProductList(start,pageSize); }
Controller实现:
@GetMapping(value = "/products/{page}/{pageSize}") public List<ProductInfo> queryProduct(@PathVariable("page")int page,@PathVariable("pageSize")int pageSize){ return productService.queryProduct(page,pageSize); }
单元 测试:
@Test public void testSelectProductList(){ List<ProductInfo> productInfos = productService.queryProduct(1,10); System.out.println(productInfos); }
通过查询商品列表接口,能够查询到所有分片的商品信息,关联的地理区域,店铺信息正确。
总结:
分页查询是业务中最常见的场景,Sharding-jdbc支持常用关系数据库的分页查询,不过Sharding-jdbc的分页功能比较容易让使用者误解,用户通常认为分页归并会占用大量内存。 在分布式的场景中,将 LIMIT 10000000 , 10改写为 LIMIT 0, 10000010 ,才能保证其数据的正确性。 用户非常容易产生ShardingSphere会将大量无意义的数据加载至内存中,造成内存溢出风险的错觉。 其实大部分情况都通过流式归并获取数据结果集,因此ShardingSphere 会通过结果集的next方法将无需取出的数据全部跳过,并不会将其存入内存。但同时需要注意的是,由于排序的需要,大量的数据仍然需要传输到Sharding-Jdbc的内存空间。 因此,采用LIMIT
这种方式分页,并非最佳实践。 由于LIMIT并不能通过索引查询数据,因此如果可以保证ID的连续性,通过ID进行分页是比较好的解决方案,例如:
SELECT * FROM t_order WHERE id > 100000 AND id <= 100010 ORDER BY id;
或通过记录上次查询结果的最后一条记录的ID进行下一页的查询,例如:
SELECT * FROM t_order WHERE id > 10000000 LIMIT 10;
排序功能是由Sharding-jdbc的排序归并来完成,由于在SQL中存在 ORDER BY 语句,因此每个数据结果集自身是有序的,因此只需要将数据结果集当前游标指向的数据值进行排序即可。 这相当于对多个有序的数组进行排序,归并排序是最适合此场景的排序算法。
5.5 统计商品
本小节实现商品总数统计,商品分组统计
Dao实现,在ProductDao中定义:
//总数统计 @Select("select count(1) from product_info") int selectCount(); //分组统计 @Select("select count(1) as num from product_info group by region_code having num>1 ORDER BY region_code ASC") List<Map> selectProductGroupList();
单元测试:
@Test public void testSelectCount(){ int i = productDao.selectCount(); System.out.println(i); } @Test public void testSelectGroupList(){ List<Map> maps = productDao.selectProductGroupList(); System.out.println(maps); }
总结:
分组统计
分组统计也是业务中常见的场景,分组功能的实现由Sharding-jdbc分组归并完成。分组归并的情况最为复杂,它分为流式分组归并和内存分组归并。 流式分组归并要求SQL的排序项与分组项的字段必须保持一致,否则只能通过内存归并才能保证其数据的正确性。
举例说明,假设根据科目分片,表结构中包含考生的姓名(为了简单起见,不考虑重名的情况)和分数。通过SQL
获取每位考生的总分,可通过如下SQL:
SELECT name, SUM(score) FROM t_score GROUP BY name ORDER BY name;
在分组项与排序项完全一致的情况下,取得的数据是连续的,分组所需的数据全数存在于各个数据结果集的当前游标所指向的数据值,因此可以采用流式归并。如下图所示。
进行归并时,逻辑与排序归并类似。 下图展现了进行next调用的时候,流式分组归并是如何进行的。
通过图中我们可以看到,当进行第一次next调用时,排在队列首位的t_score_java将会被弹出队列,并且将分组值同为“Jetty”的其他结果集中的数据一同弹出队列。 在获取了所有的姓名为“Jetty”的同学的分数之后,进行累加操作,那么,在第一次next调用结束后,取出的结果集是“Jetty”的分数总和。 与此同时,所有的数据结果集中的游标都将下移至数据值“Jetty”的下一个不同的数据值,并且根据数据结果集当前游标指向的值进行重排序。 因此,包含名字顺着第二位的“John”的相关数据结果集则排在的队列的前列。