逆向工程生成mapper.java、mapper.xml、po
使用从逆向工程说明处的模板项目
GeneratorSqlmap.java
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.exception.XMLParserException;
import org.mybatis.generator.internal.DefaultShellCallback;
public class GeneratorSqlmap {
public void generator() throws Exception{
List warnings = new ArrayList();
boolean overwrite = true;
//指定 逆向工程配置文件
File configFile = new File("generatorConfig.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}
public static void main(String[] args) throws Exception {
try {
GeneratorSqlmap generatorSqlmap = new GeneratorSqlmap();
generatorSqlmap.generator();
} catch (Exception e) {
e.printStackTrace();
}
}
}
generatorConfig.xml
运行java代码,将生成的po、mapper拷贝到主项目中,不建议在生成的代码上做操作,需要修改需求可以对生成的po、mapper进行包装。
关于po是否要实现序列化接口
序列化po主要为了实现二级缓存、分布式缓存,为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定在内存。
参考:
https://www.jianshu.com/writer#/notebooks/39849728/notes/53800305/preview
在我们的抢红包项目中对实时性要求极高,所以不采用二级缓存。所以就不考虑序列化pojo类了。
搭建ssm框架(Dao层):
新建ssm_redpacket项目,导入jar包
使用的jdk为1.8
经过自己测试导入spring4.2.4的jar包正好好使,低版本运行时会有很多找不到对应包的错误。
spring整合mybatis sqlMapConfig
新建源文件夹config-包mybatis
sqlMapConfig.xml
(没剩多少内容了,主要部分sqlSessionFactory、mapper扫描都由spring接管)
applicationContext-dao.xml
db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/red_packet
jdbc.username=root
jdbc.password=suntong
log4j.properties
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
在TRedPacketMapper.java中添加两个方法 , 一个是查询红包,另一个是扣减红包库存。
抢红包的逻辑是,先查询红包的信息,看其是否拥有存量可以扣减。如果有存量,那么可以扣减它,否则就不扣减。
package com.redpacket.ssm.mapper;
import com.redpacket.ssm.po.TRedPacket;
import com.redpacket.ssm.po.TRedPacketExample;
import java.util.List;
import org.apache.ibatis.annotations.Param;
public interface TRedPacketMapper {
int countByExample(TRedPacketExample example);
int deleteByExample(TRedPacketExample example);
int deleteByPrimaryKey(Integer id);
int insert(TRedPacket record);
int insertSelective(TRedPacket record);
List selectByExample(TRedPacketExample example);
//查询红包具体信息已包含
TRedPacket selectByPrimaryKey(Integer id);
int updateByExampleSelective(@Param("record") TRedPacket record, @Param("example") TRedPacketExample example);
int updateByExample(@Param("record") TRedPacket record, @Param("example") TRedPacketExample example);
int updateByPrimaryKeySelective(TRedPacket record);
int updateByPrimaryKey(TRedPacket record);
//新增按照id扣减红包库存
int decreaseRedPacket(Integer id);
}
对应Mapper映射文件
update t_red_packet set stock = stock - 1 where id =
#{id,jdbcType=INTEGER}
(映射太多了,只摘咱们添加的)
TUserRedPacketMapper.java
package com.redpacket.ssm.mapper;
import com.redpacket.ssm.po.TUserRedPacket;
import com.redpacket.ssm.po.TUserRedPacketExample;
import java.util.List;
import org.apache.ibatis.annotations.Param;
public interface TUserRedPacketMapper {
/**
* 插入抢红包信息.
* @param userRedPacket ——抢红包信息
* @return 影响记录数.
*/
public int grapRedPacket(TUserRedPacket tUserRedPacket);
}
对应mapper映射文件
insert into T_USER_RED_PACKET( red_packet_id, user_id, amount, grab_time, note)
values (#{redPacketId}, #{userId}, #{amount}, now(), #{note})
这里使用了 useGeneratedKeys 和 keyProperty,这就意味着会Mybatis执行完插入语句后,自动将自增长值赋值给对象的属性id。这样就可以拿到插入记录的主键了 , 关于 DAO 层就基本完成了。
web.xml
contextConfigLocation
/WEB-INF/classes/spring/applicationContext-*.xml
org.springframework.web.context.ContextLoaderListener
新建两个mapper的测试类测试对应的方法
TRedPacketMapperTest.java
package com.redpacket.ssm.test;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.redpacket.ssm.mapper.TRedPacketMapper;
import com.redpacket.ssm.po.TRedPacket;
public class TRedPacketMapperTest {
private ApplicationContext applicationContext;
//再setUp中构造spring容器
@Before
public void setUp() throws Exception{
applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-dao.xml");
}
@Test
public void testSelectByPrimaryKey() {
TRedPacketMapper tRedPacketMapper = (TRedPacketMapper)applicationContext.getBean("TRedPacketMapper");
TRedPacket tRedPacket = tRedPacketMapper.selectByPrimaryKey(1);
System.out.println(tRedPacket);
}
@Test
public void testDecreaseRedPacket() {
TRedPacketMapper tRedPacketMapper = (TRedPacketMapper)applicationContext.getBean("TRedPacketMapper");
tRedPacketMapper.decreaseRedPacket(1);
}
}
TUserRedPacketMapperTest.java
package com.redpacket.ssm.test;
import static org.junit.Assert.*;
import java.math.BigDecimal;
import java.util.Date;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.redpacket.ssm.mapper.TRedPacketMapper;
import com.redpacket.ssm.mapper.TUserRedPacketMapper;
import com.redpacket.ssm.po.TRedPacket;
import com.redpacket.ssm.po.TUserRedPacket;
public class TUserRedPacketMapperTest {
private ApplicationContext applicationContext;
//再setUp中构造spring容器
@Before
public void setUp() throws Exception{
applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-dao.xml");
}
@Test
public void testGrapRedPacket() {
TUserRedPacketMapper tUserRedPacketMapper = (TUserRedPacketMapper)applicationContext.getBean("TUserRedPacketMapper");
TUserRedPacket tUserRedPacket = new TUserRedPacket();
tUserRedPacket.setAmount(new BigDecimal(10.0));
tUserRedPacket.setGrabTime(new Date());
tUserRedPacket.setRedPacketId(1);
tUserRedPacket.setUserId(1);
tUserRedPacketMapper.grapRedPacket(tUserRedPacket);
}
}
查询后输出的结果是一个对象地址,如果想增加toString方法最好新建一个包装类,不要在原pojo上做改动,增强以后的拓展性。
增添和修改到数据库总看方便(此处忘记截图)
工程结构
搭建ssm框架(service层)
考虑到项目主要功能是测试高并发情况下如何保证数据安全,所以不用注解了,xml配置更加稳定容易,不易出现意料之外的问题(半注解半xml配置的话要在xml中开启注解配置,全注解的话要继承配置类,全注解并不爽,
半xml加注解时在xml中配置这句话,spring就能开始扫描包下所有的注解)
RedPacketService.java
package com.redpacket.ssm.service;
import com.redpacket.ssm.po.TRedPacket;
public interface RedPacketService {
/**
* 获取红包
* @param id——编号
* @return 红包信息
*/
public TRedPacket getRedPacket(Integer id);
/**
* 扣减红包
* @param id——编号
* @return 影响条数.
*/
public int decreaseRedPacket(Integer id);
}
UserRedPacketService.java
package com.redpacket.ssm.service;
public interface UserRedPacketService {
/**
* 保存抢红包信息.
* @param redPacketId 红包编号
* @param userId 抢红包用户编号
* @return 影响记录数.
*/
public int grapRedPacket(Integer redPacketId, Integer userId);
}
RedPacketServiceImpl.java
package com.redpacket.ssm.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import com.redpacket.ssm.mapper.TRedPacketMapper;
import com.redpacket.ssm.po.TRedPacket;
import com.redpacket.ssm.service.RedPacketService;
public class RedPacketServiceImpl implements RedPacketService {
@Autowired
private TRedPacketMapper tRedPacketMapper;
@Override
public TRedPacket getRedPacket(Integer id) {
return tRedPacketMapper.selectByPrimaryKey(id);
}
@Override
public int decreaseRedPacket(Integer id) {
return tRedPacketMapper.decreaseRedPacket(id);
}
}
UserRedPacketServiceImpl.java
package com.redpacket.ssm.service.impl;
import java.math.BigDecimal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import com.redpacket.ssm.mapper.TRedPacketMapper;
import com.redpacket.ssm.mapper.TUserRedPacketMapper;
import com.redpacket.ssm.po.TRedPacket;
import com.redpacket.ssm.po.TUserRedPacket;
import com.redpacket.ssm.service.UserRedPacketService;
public class UserRedPacketServiceImpl implements UserRedPacketService {
@Autowired
private TRedPacketMapper tRedPacketMapper;
@Autowired
private TUserRedPacketMapper tUserRedPacketMapper;
// 失败
final int FAILED = 0;
@Override
public int grapRedPacket(Integer redPacketId, Integer userId) {
// 获取红包信息
TRedPacket tRedPacket = tRedPacketMapper.selectByPrimaryKey(redPacketId);
int leftRedPacket = tRedPacket.getStock();
// 当前小红包库存大于0
if (leftRedPacket > 0) {
tRedPacketMapper.decreaseRedPacket(redPacketId);
// logger.info("剩余Stock数量:{}", leftRedPacket);
// 生成抢红包信息
TUserRedPacket tUserRedPacket = new TUserRedPacket();
tUserRedPacket.setRedPacketId(redPacketId);
tUserRedPacket.setUserId(userId);
tUserRedPacket.setAmount(new BigDecimal(tRedPacket.getUnitAmount()));
tUserRedPacket.setNote("redpacket- " + redPacketId);
// 插入抢红包信息
int result = tUserRedPacketMapper.grapRedPacket(tUserRedPacket);
return result;
}
// logger.info("没有红包啦.....剩余Stock数量:{}", leftRedPacket);
// 失败返回
return FAILED;
}
}
applicationContext-service.xml
applicationContext-transaction.xml
事务配置参考
https://www.jianshu.com/writer#/notebooks/39651087/notes/53434246/preview
单元测试
RedPacketServiceTest.java
package com.redpacket.ssm.test;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.redpacket.ssm.po.TRedPacket;
import com.redpacket.ssm.service.impl.RedPacketServiceImpl;
public class RedPacketServiceTest {
private ApplicationContext applicationContext;
@Before
public void setUp() throws Exception{
applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-service.xml","classpath:spring/applicationContext-dao.xml");
}
@Test
public void testGetRedPacket() {
RedPacketServiceImpl redPacketServiceImpl = (RedPacketServiceImpl)applicationContext.getBean("redPacketService");
TRedPacket tRedPacket = redPacketServiceImpl.getRedPacket(1);
System.out.println(tRedPacket);
}
@Test
public void testDecreaseRedPacket() {
RedPacketServiceImpl redPacketServiceImpl = (RedPacketServiceImpl)applicationContext.getBean("redPacketService");
int a = redPacketServiceImpl.decreaseRedPacket(1);
System.out.println(a);
}
}
测试方法testDecreaseRedPacket()
测试方法testGetRedPacket()
UserRedPacketServiceImplTest.java
package com.redpacket.ssm.test;
import static org.junit.Assert.*;
import java.math.BigDecimal;
import java.util.Date;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.redpacket.ssm.service.impl.UserRedPacketServiceImpl;
public class UserRedPacketServiceImplTest {
private ApplicationContext applicationContext;
@Before
public void setUp() throws Exception{
applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-service.xml","classpath:spring/applicationContext-dao.xml");
}
@Test
public void testGrapRedPacket() {
UserRedPacketServiceImpl userRedPacketServiceImpl = (UserRedPacketServiceImpl)applicationContext.getBean("userRedPacketService");
int a = userRedPacketServiceImpl.grapRedPacket(1, 2);
System.out.println(a);
}
}
绿了绿了!!!绿了就好使了
搭建ssm框架(Controller层):
UserRedPacketController.java
package com.redpacket.ssm.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.redpacket.ssm.service.UserRedPacketService;
@Controller
@RequestMapping("/userRedPacket")
public class UserRedPacketController {
@Autowired
private UserRedPacketService userRedPacketService;
@RequestMapping("/grapRedPacket")
public @ResponseBody Map grapRedPacket(Integer redPacketId, Integer userId) {
// 抢红包
int result = userRedPacketService.grapRedPacket(redPacketId, userId);
Map retMap = new HashMap();
boolean flag = result > 0;
retMap.put("success", flag);
retMap.put("message", flag ? "抢红包成功" : "抢红包失败");
return retMap;
}
}
DateConverter.java
package com.redpacket.ssm.controller.converter;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.core.convert.converter.Converter;
public class DateConverter implements Converter {
@Override
public Date convert(String source) {
//实现日期串转成日期类型(格式"yyyy-MM-dd HH:mm:ss")
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//转成直接返回
return simpleDateFormat.parse(source);
} catch (Exception e) {
e.printStackTrace();
}
//如果参数绑定失败返回null
return null;
}
}
实现日期串转换成日期类型
springmvc.xml
5242880
web.xml
ssm_redpacket
contextConfigLocation
/WEB-INF/classes/spring/applicationContext-*.xml
org.springframework.web.context.ContextLoaderListener
springmvc
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring/springmvc.xml
springmvc
*.action
CharacterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
utf-8
CharacterEncodingFilter
/*
index.html
index.htm
index.jsp
default.html
default.htm
default.jsp
redpacket.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
参数
haha
模拟30000个用户同时进行抢红包操作
项目结构
此时进行高并发访问数据库不会出现超发问题,因为tomcat将数据库访问线程池中最大线程设置为了150,最小只有15,采用的还是BIO(阻塞型IO),刚并发性能很差,mysql毫无压力。。。
修改最大访问线程为5000,协议选择NIO,参考:
https://blog.csdn.net/dc282614966/article/details/81186783
此时就可以进行高并发访问了
访问http://localhost:8080/ssm_redpacket/redpacket.jsp
开始模拟高并发情况(注意使用火狐浏览器)
使用 SQL 去查询红包的库存、发放红包的总个数、总金额,我们发现了错误,红包总额为 20 万元,两万个小红包,结果发放了 200020元的红包, 20002 个红包。现有库存为-2,超出了之前的限定,这就是高并发的超发现象,这是一个错误的逻辑 。
SELECT
a.id,
a.amount,
a.stock
FROM
T_RED_PACKET a
WHERE
a.id = 1
UNION ALL
SELECT
max(b.user_id),
sum(b.amount),
count(*)
FROM
T_USER_RED_PACKET b
WHERE
b.red_packet_id = 1;
一共使用了 50 秒的时间,完成 20002 个红包的抢夺,性能一般。。。但是逻辑上存在超发错误,还需要解决超发问题 。
SELECT
(
UNIX_TIMESTAMP(max(a.grab_time)) - UNIX_TIMESTAMP(min(a.grab_time))
) AS lastTime
FROM
T_USER_RED_PACKET a;
超发问题解决思路:
超发现象是由多线程下数据不一致造成的,对于此类问题,如果采用数据库方案的话,主要通过悲观锁和乐观锁来处理,这两种方法的性能是不一样的。
接下来我们分别使用悲观锁、乐观锁、Redis+lua的方式来解决这个超发问题。