目录
前端:
数据库:
过程
第一步:
第二步:
第三步
但是有问题:
“立即投资”,输入投资金额
对投资金额的要求:非0、非负、非空、100的整数倍
收益金额=投资金额*利率
理财:年利率;借钱:天利率
本项目中:收益率=投资金额*日利率*投资天数
账户表
产品表:剩余可投金额
投资记录表
p2p-web
BidInfoController
@Controller
public class BidInfoController {
@Autowired
private BidInfoService bidInfoService;
@RequestMapping(value = "/loan/invest")
public @ResponseBody Object invest(HttpServletRequest request,
@RequestParam (value = "loanId",required = true) Integer loanId,
@RequestParam (value = "bidMoney",required = true) Double bidMoney) {
Map retMap = new HashMap();
//从session中获取用户的信息
User sesionUser = (User) request.getSession().getAttribute(Constants.SESSION_USER);
//准备请求参数
Map paramMap = new HashMap();
//用户标识
paramMap.put("uid",sesionUser.getId());
//产品标识
paramMap.put("loanId",loanId);
//投资金额
paramMap.put("bidMoney",bidMoney);
//手机号码
paramMap.put("phone",sesionUser.getPhone());
//用户投资(用户标识,产品标识,投资金额) -> 返回结果ResultObject
ResultObject resultObject = bidInfoService.invest(paramMap);
//判断是否成功
if (StringUtils.equals(Constants.SUCCESS,resultObject.getErrorCode())) {
retMap.put(Constants.ERROR_MESSAGE,Constants.OK);
} else {
retMap.put(Constants.ERROR_MESSAGE,"投资人数过多,请稍后重试...");
return retMap;
}
return retMap;
}
}
过程:
1、从session中获取用户的信息
2、用户投资(用户标识,产品标识,投资金额) -> 返回结果ResultObject(后面的第二步)
3、根据返回结果来 判断是否成功
p2p-exterface
BidInfoService.java
/**
* 用户投资
* @param paramMap
* @return
*/
ResultObject invest(Map paramMap);
第二步中的具体实现
p2p-dataservice
BidInfoServiceImpl.java
先放一个整体代码
@Override
public ResultObject invest(Map paramMap) {
ResultObject resultObject = new ResultObject();
resultObject.setErrorCode(Constants.SUCCESS);
//超卖现象:实际销售的数量超过了原有销售数量
//使用数据库乐观锁解决超卖
//获取产品的版本号
LoanInfo loanInfo = loanInfoMapper.selectByPrimaryKey((Integer) paramMap.get("loanId"));
paramMap.put("version",loanInfo.getVersion());
//更新产品剩余可投金额
int updateLeftProductMoneyCount = loanInfoMapper.updateLeftProductMoneyByLoanId(paramMap);
if (updateLeftProductMoneyCount > 0) {
//更新帐户可用余额
int updateFinanceAccountCount = financeAccountMapper.updateFinanceAccountByBid(paramMap);
if (updateFinanceAccountCount > 0) {
//新增投资记录
BidInfo bidInfo = new BidInfo();
//用户标识
bidInfo.setUid((Integer) paramMap.get("uid"));
//产品标识
bidInfo.setLoanId((Integer) paramMap.get("loanId"));
//投资金额
bidInfo.setBidMoney((Double) paramMap.get("bidMoney"));
//投资时间
bidInfo.setBidTime(new Date());
//投资状态
bidInfo.setBidStatus(1);
int insertBidCount = bidInfoMapper.insertSelective(bidInfo);
if (insertBidCount > 0) {
//再次查看产品的剩余可投金额是否为0
LoanInfo loanDetail = loanInfoMapper.selectByPrimaryKey((Integer) paramMap.get("loanId"));
//为0:更新产品的状态及满标时间
if (0 == loanDetail.getLeftProductMoney()) {
//更新产品的状态及满标时间
LoanInfo updateLoanInfo = new LoanInfo();
updateLoanInfo.setId(loanDetail.getId());
updateLoanInfo.setProductFullTime(new Date());
updateLoanInfo.setProductStatus(1);//0未满标,1已满标,2满标且生成收益讲
int updateLoanInfoCount = loanInfoMapper.updateByPrimaryKeySelective(updateLoanInfo);
if (updateLoanInfoCount < 0) {
resultObject.setErrorCode(Constants.FAIL);
}
}
String phone = (String) paramMap.get("phone");
//将用户的投资金额存放到redis缓存中
redisTemplate.opsForZSet().incrementScore(Constants.INVEST_TOP, phone,(Double) paramMap.get("bidMoney"));
} else {
resultObject.setErrorCode(Constants.FAIL);
}
} else {
resultObject.setErrorCode(Constants.FAIL);
}
} else {
resultObject.setErrorCode(Constants.FAIL);
}
return resultObject;
}
1、获取产品的版本号
LoanInfo loanInfo = loanInfoMapper.selectByPrimaryKey((Integer) paramMap.get("loanId"));
paramMap.put("version",loanInfo.getVersion());
2、更新产品的剩余可投金额(超卖)
int updateLeftProductMoneyCount = loanInfoMapper.updateLeftProductMoneyByLoanId(paramMap);
就是这里出现了超卖现象--用了乐观锁来解决
进入方法updateLeftProductMoneyByLoanId
update
b_loan_info
set
left_product_money = left_product_money - #{bidMoney},
version = version+1
where
id = #{loanId} and version = #{version} and (left_product_money - #{bidMoney}) >= 0
超卖现象:实际投资的金额 超过了 原有可以投资的金额
在多线程、高并发时经常发生
在数据库,投资产品信息表中有一个“版本号”,version
后台先获取版本号,再去更新产品剩余可投金额
做了个测试
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
//获取spring容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
//获取指定的bean
BidInfoService bidInfoService = (BidInfoService) context.getBean("bidInfoServiceImpl");
//创建一个固定的线程
ExecutorService executorService = Executors.newFixedThreadPool(100);
Map paramMap = new HashMap();
paramMap.put("uid",1);
paramMap.put("loanId",3);
paramMap.put("bidMoney",1.0);
for (int i = 0 ; i < 1000; i++) {
executorService.submit(new Callable(){
@Override
public Object call() throws Exception {
return bidInfoService.invest(paramMap);
}
});
}
executorService.shutdown();
}
}
过程:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
BidInfoService bidInfoService = (BidInfoService) context.getBean("bidInfoServiceImpl");
ExecutorService executorService = Executors.newFixedThreadPool(100);
Map paramMap = new HashMap();
paramMap.put("uid",1);
paramMap.put("loanId",3);
paramMap.put("bidMoney",1.0);
for (int i = 0 ; i < 1000; i++) {
executorService.submit(new Callable(){
@Override
public Object call() throws Exception {
return bidInfoService.invest(paramMap);
}
});
}
executorService.shutdown();
结果就是不用乐观锁,会有问题;用了乐观锁,就没有问题了
3、更新账户可用余额
第2步成功后,可以更新账户可用余额
这里不需要乐观锁
int updateFinanceAccountCount = financeAccountMapper.updateFinanceAccountByBid(paramMap);
进入方法:
updateFinanceAccountByBid(paramMap)
update
u_finance_account
set
available_money = available_money - #{bidMoney}
where
uid = #{uid} and (available_money - #{bidMoney}) >= 0
4、新增投资记录
//新增投资记录
BidInfo bidInfo = new BidInfo();
//用户标识
bidInfo.setUid((Integer) paramMap.get("uid"));
//产品标识
bidInfo.setLoanId((Integer) paramMap.get("loanId"));
//投资金额
bidInfo.setBidMoney((Double) paramMap.get("bidMoney"));
//投资时间
bidInfo.setBidTime(new Date());
//投资状态
bidInfo.setBidStatus(1);
int insertBidCount = bidInfoMapper.insertSelective(bidInfo);
进入方法:
insertSelective
insert into b_bid_info
id,
loan_id,
uid,
bid_money,
bid_time,
bid_status,
#{id,jdbcType=INTEGER},
#{loanId,jdbcType=INTEGER},
#{uid,jdbcType=INTEGER},
#{bidMoney,jdbcType=DOUBLE},
#{bidTime,jdbcType=TIMESTAMP},
#{bidStatus,jdbcType=INTEGER},
5、再次查看产品的剩余可投金额
LoanInfo loanDetail = loanInfoMapper.selectByPrimaryKey((Integer) paramMap.get("loanId"));
如果为0:更新产品的状态及满标时间
//更新产品的状态及满标时间
LoanInfo updateLoanInfo = new LoanInfo();
updateLoanInfo.setId(loanDetail.getId());
updateLoanInfo.setProductFullTime(new Date());
updateLoanInfo.setProductStatus(1);//0未满标,1已满标,2满标且生成收益讲
int updateLoanInfoCount = loanInfoMapper.updateByPrimaryKeySelective(updateLoanInfo);
if (updateLoanInfoCount < 0) {
resultObject.setErrorCode(Constants.FAIL);
}
6、将用户的投资金额存放到redis缓存中
redisTemplate.opsForZSet().incrementScore(Constants.INVEST_TOP, phone,(Double) paramMap.get("bidMoney"));
如果中间有那个过程失败了;就会返回:
resultObject.setErrorCode(Constants.FAIL);
最后成功返回:
return resultObject;
1、怎么保证事务?
2、超卖现象还可以怎么改进呢?
3、如果版本号不一致, 就是会返回用户:稍后再试。。。。
那同时10个人操作,9个人会失败
思考:可不可以写在一个while循环里呢?(读版本号,再尝试更改剩余可投金额)?
不行,while 会一直连库 占据数据库链接,数据库连接池 可是很宝贵的资源
思考:这个可以放弃版本号,预减一操作就好、
思考:redis、消息队列