逆向工程生成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<String> warnings = new ArrayList<String>();
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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="testTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/red_packet" userId="root"
password="suntong">
</jdbcConnection>
<!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver"
connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg"
userId="yycg"
password="yycg">
</jdbcConnection> -->
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和
NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成PO类的位置 -->
<javaModelGenerator targetPackage="com.redpacket.ssm.po"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="com.redpacket.ssm.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.redpacket.ssm.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定数据库表 -->
<table tableName="t_red_packet"></table>
<table tableName="t_user_red_packet"></table>
<!-- <table schema="" tableName="sys_user"></table>
<table schema="" tableName="sys_role"></table>
<table schema="" tableName="sys_permission"></table>
<table schema="" tableName="sys_user_role"></table>
<table schema="" tableName="sys_role_permission"></table> -->
<!-- 有些表的字段需要指定java类型
<table schema="" tableName="">
<columnOverride column="" javaType="" />
</table> -->
</context>
</generatorConfiguration>
运行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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 全局setting配置
根据需要加
-->
<!-- 配置别名 -->
<typeAliases>
<!-- 批量扫描别名 -->
<package name="com.redpacket.ssm.po"/>
</typeAliases>
<!-- 配置mapper
由于使用spring和mybatis的整合包进行mapper扫描,这里不需要配置
必须遵循:mapper.xml和mapper.java文件同名,且在一个目录
-->
</configuration>
(没剩多少内容了,主要部分sqlSessionFactory、mapper扫描都由spring接管)
applicationContext-dao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 全局setting配置
根据需要加
-->
<!-- 配置别名 -->
<typeAliases>
<!-- 批量扫描别名 -->
<package name="com.redpacket.ssm.po"/>
</typeAliases>
<!-- 配置mapper
由于使用spring和mybatis的整合包进行mapper扫描,这里不需要配置
必须遵循:mapper.xml和mapper.java文件同名,且在一个目录
-->
</configuration>
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<TRedPacket> 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 id="decreaseRedPacket">
update t_red_packet set stock = stock - 1 where id =
#{id,jdbcType=INTEGER}
</update>
(映射太多了,只摘咱们添加的)
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 id="grapRedPacket" useGeneratedKeys="true"
keyProperty="id" parameterType="com.redpacket.ssm.po.TUserRedPacket">
insert into T_USER_RED_PACKET( red_packet_id, user_id, amount, grab_time, note)
values (#{redPacketId}, #{userId}, #{amount}, now(), #{note})
</insert>
这里使用了 useGeneratedKeys 和 keyProperty,这就意味着会Mybatis执行完插入语句后,自动将自增长值赋值给对象的属性id。这样就可以拿到插入记录的主键了 , 关于 DAO 层就基本完成了。
web.xml
<!-- 加载spring容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/spring/applicationContext-*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
新建两个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中开启注解配置,全注解的话要继承配置类,全注解并不爽,
<!-- 指定扫描com.fei.bean包下的所有类中的注解 -->
<context:component-scan base-package="com.fei.bean"></context:component-scan>
半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
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 商品管理的service -->
<bean id="redPacketService" class="com.redpacket.ssm.service.impl.RedPacketServiceImpl"/>
<bean id="userRedPacketService" class="com.redpacket.ssm.service.impl.UserRedPacketServiceImpl"/>
</beans>
applicationContext-transaction.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 事务管理器
对mybatis操作数据库事务控制,spring使用jdbc的事务控制类
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源
dataSource在applicationContext-dao.xml中配置了
-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 传播行为 -->
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="grap*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="decrease*" propagation="REQUIRED"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- aop -->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* cn.itcast.ssm.service.impl.*.*(..))"/>
</aop:config>
</beans>
事务配置参考
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<String, Object> grapRedPacket(Integer redPacketId, Integer userId) {
// 抢红包
int result = userRedPacketService.grapRedPacket(redPacketId, userId);
Map<String, Object> retMap = new HashMap<String, Object>();
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<String, Date> {
@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
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 可以扫描controller、service、...
这里让扫描controller,指定controller的包
-->
<context:component-scan base-package="com.redpacket.ssm.controller"></context:component-scan>
<!-- 使用 mvc:annotation-driven代替上边注解映射器和注解适配器配置
mvc:annotation-driven默认加载很多的参数绑定方法,
比如json转换解析器就默认加载了,如果使用mvc:annotation-driven不用配置上边的RequestMappingHandlerMapping和RequestMappingHandlerAdapter
实际开发时使用mvc:annotation-driven
-->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<!-- 自定义参数绑定 -->
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<!-- 转换器 -->
<property name="converters">
<list>
<!-- 日期类型转换 -->
<bean class="com.redpacket.ssm.controller.converter.DateConverter"/>
</list>
</property>
</bean>
<!-- 视图解析器
解析jsp解析,默认使用jstl标签,classpath下的得有jstl的包
-->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 配置jsp路径的前缀 -->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!-- 配置jsp路径的后缀 -->
<property name="suffix" value=".jsp"/>
</bean>
<!-- 文件上传 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置上传文件的最大尺寸为5MB -->
<property name="maxUploadSize">
<value>5242880</value>
</property>
</bean>
</beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>ssm_redpacket</display-name>
<!-- 加载spring容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/spring/applicationContext-*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- springmvc前端控制器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- contextConfigLocation配置springmvc加载的配置文件(配置处理器映射器、适配器等等) 如果不配置contextConfigLocation,默认加载的是/WEB-INF/servlet名称-serlvet.xml(springmvc-servlet.xml) -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!-- 第一种:*.action,访问以.action结尾 由DispatcherServlet进行解析 第二种:/,所以访问的地址都由DispatcherServlet进行解析,对于静态文件的解析需要配置不让DispatcherServlet进行解析
使用此种方式可以实现 RESTful风格的url 第三种:/*,这样配置不对,使用这种配置,最终要转发到一个jsp页面时, 仍然会由DispatcherServlet解析jsp地址,不能根据jsp页面找到handler,会报错。 -->
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<!-- post乱码过虑器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>
redpacket.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>参数</title>
<!-- 加载Query文件-->
<script type="text/javascript" src="https://code.jquery.com/jquery-3.2.0.js">
</script>
<script type="text/javascript">
$(document).ready(function () {
//模拟30000个异步请求,进行并发
var max = 30000;
for (var i = 1; i <= max; i++) {
//jQuery的post请求,请注意这是异步请求
$.post({
//请求抢id为1的红包
//根据自己请求修改对应的url和大红包编号
url: "${pageContext.request.contextPath }/userRedPacket/grapRedPacket.action?redPacketId=1&userId=" + i,
//成功后的方法
success: function (result) {
}
});
}
});
</script>
</head>
<body>
haha
</body>
</html>
模拟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的方式来解决这个超发问题。