18_生成订单

本文详细解析了在线商城从用户点击提交订单按钮到生成订单的整个流程,包括需求分析、步骤分解、代码实现及事务控制等内容,为理解电商网站后台逻辑提供了清晰的指导。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

生成订单

  • 需求与步骤分析
  • 准备工作

1)需求与步骤分析

需求分析:
在购物车页面上,有一个提交订单按钮,点击的时候将用户购物车中的商品添加到数据库中
实体:
用户
订单
订单项
商品
我们在第一章分析时已经分析过了他们的关系:
在这里插入图片描述
步骤分析:

  • 点击生成订单:${path}/order/add
  • 创建OrderServlet:
    • 处理add,创建add方法。1. 判断用户是否登录,2. 封装数据Order
    • 调用orderService生成订单。1. 向订单表中插入一条数据,2. 向订单项表中插入多条数据
      要么成功,要么失败,所以要使用事务控制

2)准备工作

① 创建数据表

在这里插入图片描述

② 创建实体类

订单实体Order:

public class Order implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = -8541282704453921798L;	
	private String oid;
	/**
	 * 下单时间
	 */
	private Date ordertime;
	/**
	 * 总价
	 */
	private Double total;
	/**
	 * 状态 0 未支付,1 已支付 2 
	 */
	private Integer state;
	/**
	 * 订单收件地址
	 */
	private String address;
	/**
	 * 收件人
	 */
	private String name;
	/**
	 * 收件号码
	 */
	private String telephone;
	/**
	 * 属于哪个用户
	 */
	private User user;
	/**
	 * 一个订单包含多个订单项
	 */
	private List<OrderItem> items=new ArrayList<>();
	
	//getter和setter...	
}

订单项实体OrderItem:

public class OrderItem implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = -2760996836863504723L;
	/**
	 * 订单项id
	 */
	private String itemid;
	/**
	 * 订单项商品总数量
	 */
	private Integer count;
	/**
	 * 当前订单项金额
	 */
	private Double subtotal;
	/**
	 * 包含哪个实体
	 */
	private Product product;
	/**
	 * 属于哪个订单
	 */
	private Order order;
	//getter和setter...
}
③ 创建Order与OrderItem的dao层和service层接口和实现类

略:


3)代码实现

① 编写CardServlet处理add,生成订单
	/**
	 * 生成订单
	 * @param request
	 * @param response
	 * @throws IOException 
	 * @throws ServletException 
	 */
	private void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1、判断用户是否登录
		User user=(User) request.getSession().getAttribute("user");
		if(user == null) {
			request.setAttribute("msg", "请先登录~~");
			request.getRequestDispatcher("/jsp/msg.jsp").forward(request, response);
			return;
		}
		
		//2、封装数据
		Order order=new Order();
		
		//2.1 订单id
		order.setOid(UUIDUtils.getId());
		
		//2.2 订单时间
		order.setOrdertime(new Date());
		
		//2.3 总金额
		//获取session中的cart
		Cart cart=(Cart) request.getSession().getAttribute("cart");
		order.setTotal(cart.getTotal());
		
		//2.4 订单的所有订单项
		/*
		 * 先获取cart中的items
		 * 遍历items 组装成orderItem
		 * 将orderItem添加到list(itmes)中
		 */
		for(CartItem cartItem:cart.getItems()) {
			//创建订单项
			OrderItem orderItem=new OrderItem();
			//2.4.1 设置订单项id
			orderItem.setItemid(UUIDUtils.getId());
			//2.4.2 设置购买数量
			orderItem.setCount(cartItem.getCount());
			//2.4.3 设置小计
			orderItem.setSubtotal(cartItem.getSubtotal());
			//2.4.5 设置商品
			orderItem.setProduct(cartItem.getProduct());
			//2.4.6 设置order
			orderItem.setOrder(order);
			//2.4.7 添加到list
			order.getItems().add(orderItem);
			
		}
		//2.5 设置用户
		order.setUser(user);
		//3、调用service 添加订单
		orderService.add(order);
		//4、将order绑定到request中,请求转发到/jsp/order_info.jsp
		request.setAttribute("order", order);
		request.getRequestDispatcher("/jsp/order_info.jsp").forward(request, response);
	}
② 更改DBUtil,提供操作事务控制的方法

开启事务方法:

	/**
	 * 开启事务
	 */
	public static void startTransaction() {
		try {
			getConnection().setAutoCommit(false);
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

事务提交并释放资源:

	/**
	 * 事务提交并关闭
	 */
	public static void commitAndClose() {
		Connection connection = getConnection();
		try {
			connection.commit();
			connection.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

事务回滚并释放资源:

	/**
	 * 事务回滚并关闭
	 */
	public static void rollbackAndClose() {
		Connection connection = getConnection();
		try {
			connection.rollback();
			connection.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
③ 在service层生成订单,使用事务控制
	@Override
	public void add(Order order) throws Exception{
		try {
			//1、开启事务
			DBUtil.startTransaction();
			//2、向orders表中添加一条记录
			orderDao.add(order);
			//3、向orderitem表中添加多条记录
			for(OrderItem orderItem:order.getItems()) {
				orderDao.addItem(orderItem);
			}
			//4、事务处理
			DBUtil.commitAndClose();
		}catch(Exception e) {
			e.printStackTrace();
			//错误回滚
			DBUtil.rollbackAndClose();
			throw e;
		}
	}
④ 在dao层实现增加
	public class OrderDaoImpl implements OrderDao{
		//QueryRunner
		private QueryRunner qr=new QueryRunner(DBUtil.getDataSource());
		@Override
		public void add(Order order) throws SQLException {
			String sql="insert into `orders` values(?,?,?,?,?,?,?,?)";
			qr.update(sql,order.getOid(),order.getOrdertime(),order.getTotal(),
					order.getState(),order.getAddress(),order.getName(),
					order.getTelephone(),order.getUser().getUid());
		}
	
		@Override
		public void addItem(OrderItem orderItem) throws SQLException {
			String sql="insert into `orderitem` values(?,?,?,?,?)";
			qr.update(sql,orderItem.getItemid(),orderItem.getCount(),orderItem.getSubtotal()
					,orderItem.getProduct().getPid(),orderItem.getOrder().getOid());
		}
	}
⑤ 先测试事务

先把商品加入购物车,点击提交订单:
在这里插入图片描述

查看数据库是否添加成功:
订单表:
在这里插入图片描述
订单项表:
在这里插入图片描述
修改service,手动添加异常测试事务回滚:

	...
	//2、向orders表中添加一条记录
	orderDao.add(order);
	
	int a=1/0;
	
	//3、向orderitem表中添加多条记录
	...

再进行测试:
在这里插入图片描述
提交订单,查看数据库:
在这里插入图片描述
增加成功,事务并没有回滚!!!把这个没有任何关联的数据删除掉
原因:因为在dao层执行的时候时重新获取的Connection,跟Service层处理事务时用的不是同一个连接,当然不会成功

先修改DBUtil的getConnection方法从线程池中获取
	//1、定义一个连接池
	private static BasicDataSource ds;
	//2、定义一个线程池
	private static ThreadLocal<Connection> tl=new ThreadLocal<>();
	
	/**
	 * 从线程中获取连接
	 * @return
	 * @throws SQLException
	 */
	public static Connection getConnection() {
		//从线程中获取connection
		Connection conn = tl.get();
		if(conn==null){
			try {
				conn=ds.getConnection();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			//和当前线程绑�?
			tl.set(conn);
		}
		return conn;
	}
	//...
再修改Dao层获取连接的方式

在这里插入图片描述
在测试发现事务成功!

⑥ 在order_info.jsp上展示数据

在这里插入图片描述

⑦ 在生成订单之后清空购物车
	...
	//4、将order绑定到request中,请求转发到/jsp/order_info.jsp
	request.setAttribute("order", order);
	//5、清空购物车
	request.getSession().removeAttribute("cart");
	...
<think>嗯,用户现在想了解MySQL中订单生成策略的最佳实践。首先,我需要回顾一下之前的对话历史。用户之前询问了关于MySQL JSON类型字段的使用,现在转向订单生成策略,这说明他们可能在设计电商或订单管理系统,需要处理订单号的唯一性、可扩展性等问题。 用户提到的引用里提到了多对多关系可能导致模型复杂,所以在设计订单生成时,可能需要考虑如何简化数据模型,避免复杂的关联。不过订单生成本身更多是唯一标识符的生成策略,可能与数据库设计的关系不大,但需要确保生成订单号在系统内唯一,并且高效。 订单生成的关键点通常包括唯一性、可读性、扩展性、安全性和性能。我需要考虑不同的生成方法,比如自增ID、UUID、时间戳+序列、分布式算法(如雪花算法)、业务前缀组合等。每种方法都有优缺点,比如自增ID虽然简单但可能暴露业务量,UUID全局唯一但长度过长,雪花算法适合分布式系统但需要维护机器ID。 用户可能还需要知道如何在MySQL中具体实现这些策略。例如,使用自增字段结合触发器生成订单号,或者在应用层生成后插入数据库。还要考虑索引优化,因为订单号通常作为查询条件,需要合适的索引来加速查询。 另外,用户提到的引用中有使用SpringBoot和MySQL的开发经验,可能他们是在Java生态系统中开发,所以可能需要结合Java的库来生成分布式ID,比如使用Hutool的Snowflake类。同时,需要提醒他们注意时钟回拨问题,这在雪花算法中是个常见问题,需要处理。 还需要考虑业务需求,比如是否需要包含日期、地区、业务线等信息在订单号中,便于人工识别和分类。例如,电商平台可能希望订单号包含日期和渠道代码,这样客服处理时更容易定位问题。 安全性方面,避免使用连续的数字,防止被猜测订单量,可以采用哈希或随机数。此外,分库分表时的订单号唯一性也需要考虑,可能需要将分片信息编码到订单号中。 最后,需要总结最佳实践,给出具体的实现示例,比如MySQL的自增ID结合前缀、应用层雪花算法、业务组合模式等,并比较它们的优缺点,帮助用户根据具体场景选择合适的方法。</think>--- ### MySQL 订单生成策略最佳实践 --- #### 一、核心设计原则 1. **唯一性保证** - 全系统生命周期内不重复(包括分库分表场景) - 推荐使用全局唯一算法(如雪花算法、UUID-7)代替数据库自增ID[^1] 2. **可读性要求** - 包含时间戳(如`20230815123456`) - 可添加业务标识(如`EC`代表电商订单) ```sql /* 示例订单号结构 */ EC-20230815-1200-9527-xyz123 │ │ │ │ └──随机后缀 │ │ │ └──自增序列号 │ │ └──时间精确到分钟 │ └──日期 └──业务类型 ``` 3. **安全性考量** - 避免连续数字暴露业务规模 - 可添加哈希校验位(如最后2位为哈希值)[^2] --- #### 二、推荐实现方案 ##### 方案1:MySQL自增字段+自定义规则 ```sql CREATE TABLE orders ( order_id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) GENERATED ALWAYS AS ( CONCAT('EC', DATE_FORMAT(created_at, '%Y%m%d%H%i'), LPAD(order_id, 8, '0')) ) STORED, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); ``` **优点**:数据库原生支持,无需应用层干预 **缺点**:分库分表时需改造自增逻辑 ##### 方案2:应用层雪花算法生成 ```java // Java示例(使用Hutool工具) Snowflake snowflake = IdUtil.getSnowflake(1, 1); String orderNo = "EC-" + DateUtil.format(new Date(), "yyyyMMddHHmm") + "-" + snowflake.nextIdStr(); ``` **特点**: - 支持每秒生成$2^{18} = 262,144$个ID - 包含时间戳、机器ID、序列号等信息[^3] ##### 方案3:业务组合模式 ```sql /* 订单号结构示例 */ SET @prefix = 'EC'; SET @timestamp = UNIX_TIMESTAMP(); -- 1692067200 SET @shard_id = 5; -- 分片标识 SET @seq = NEXT VALUE FOR order_seq; SELECT CONCAT( @prefix, LPAD(CONV(@timestamp, 10, 36), 6, '0'), -- 6位base36编码 LPAD(@shard_id, 2, '0'), LPAD(@seq % 10000, 4, '0') ) AS order_no; ``` **输出示例**:`EC01A3KP050123` --- #### 三、性能优化要点 1. **索引策略** ```sql ALTER TABLE orders ADD UNIQUE INDEX idx_order_no (order_no(10)); -- 前缀索引 ``` 2. **批量生成优化** ```sql INSERT INTO orders (created_at) VALUES (NOW()), (NOW()), (NOW()); -- 批量插入后更新订单号 ``` 3. **热点问题规避** - 在时间戳后添加随机后缀(如`yyyyMMddHHmmss-RR`) - 使用非连续数值生成策略 --- #### 四、特殊场景处理 1. **分库分表方案** ```sql /* 包含分片标识的订单号 */ EC-230815-05-0123456789ABCDEF │ │ │ └──UUID │ │ └──分片ID │ └──日期简 └──业务标识 ``` 2. **幂等性保障** ```sql CREATE TABLE idempotent_keys ( key_hash CHAR(64) PRIMARY KEY, order_no VARCHAR(32) ) ENGINE=InnoDB; ``` --- #### 五、方案对比 | 方案 | 唯一性 | 可读性 | 分库支持 | 性能 | |----------------------|-------|--------|---------|--------| | 数据库自增 | ✅ | ❌ | ❌ | 10k/s | | 雪花算法 | ✅ | ✅ | ✅ | 200k/s | | UUIDv7 | ✅ | ✅ | ✅ | 150k/s | | Redis原子操作 | ✅ | ✅ | ✅ | 50k/s | --- #### 六、实践建议 1. **医疗/金融场景** - 使用带校验位的方案:`EC20230815123456-9527-AB` - 增加审核流水号关联字段 2. **电商秒杀场景** - 预生成订单号池(使用Redis List缓存) - 采用异步生成机制 3. **国际业务场景** ```sql /* 包含时区标识 */ US-20230815T1200Z-05-012345 ``` --- ### 典型问题解答 **Q:订单号是否需要加密?** A:推荐业务敏感场景使用可逆加密(如AES),但需注意: - 加密后长度会增加$ \lceil \frac{n}{16} \rceil \times 24 $(Base64编码) - 需维护密钥轮换机制[^4] **Q:如何解决时间回拨问题?** A:雪花算法实现时应包含: ```java // Java伪代码 if (currentMillis < lastMillis) { throw new ClockMovedBackwardsException(); } // 或使用扩展位缓冲回拨时间 ``` ---
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

robona

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值