chpt1 项目框架搭建
1.1 springboot环境搭建
springMVC需要大量配置(ViewResolver等),spring追求“零配置”。
spring官网https://spring.io/ (project—>SpringBoot)
第一步 加依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
新建项目的随手截图
*IDEA的project可以包含多个model,可直接导入model项目,model相当于Eclipse中的project。
第二步启动类
@SpringBootApplication
public class MainApplication{
public static void main(String[] args) {
SpringApplication.run(MainApplication.class,args);
}
}
第三步建立目录包结构
*properties文件语法是key=value的形式,注释加#;此外还有.yaml文件,对应yml文件语法,yml语法比xml语法更简洁。yml语法,下文thymeleaf的application配置
- controller
服务定位 @Controller,@RequestMapping(“/url”)
信息传递 Model,@ModelAttribute
(๑•̀ㅂ•́)و✧ Model相关(作用+用法) - dao
- service
1.2 集成Thymeleaf,Result结果封装
依赖导入—application配置—html名称空间引入
//thymeleaf依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <!--引入thymeleaf名称空间-->
<meta charset="UTF-8">
<title>hello</title>
</head>
<body>
<div th:text = "${modelname}">离线数据</div>
<!--th:text,thymeleaf文本替换 $里不允许有汉字 -->
<!--<p th:text = "${model的name}"></p>,这里可以有汉字-->
</body>
</html>
result包下,新建CodeMsg类和Result类,Result属性包括错误码信息CodeMsg(code + msg)、泛型类型data。如下分别,构造函数-属性-方法。
(?为啥要这样定义,每种方法存在的意义,换种写法可行不)——
Result是一个总的结果,msg+code是用于错误输出,对应error方法,而data主要是成功之后返回成功的数据data,对应success方法
public class Result<T> {
private int code;
private String msg;
private T data;
private Result(T data) {
this.data = data;
}
private Result(int code,String msg){
this.code = code;
this.msg = msg;
}
private Result(CodeMsg codeMsg){
if (codeMsg != null){
this.code = codeMsg.getCode();
this.msg = codeMsg.getMsg();
}
}
public int getCode(){
return code;
}
public void setCode(int code){
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
/**
* 成功时调用
*/
public static <T> Result<T> success(T data){
return new Result<T>(data);
}
/**
* 失败
*/
public static <T> Result<T> error(CodeMsg codeMsg){
return new Result<T>(codeMsg);
}
}
public class CodeMsg {
private int code;
private String msg;
//通用的错误码
public static CodeMsg SUCCESS = new CodeMsg(0, "success");
public static CodeMsg SERVER_ERROR = new CodeMsg(500100, "服务端异常");
public static CodeMsg BIND_ERROR = new CodeMsg(500101, "参数校验异常:%s");
public static CodeMsg REQUEST_ILLEGER = new CodeMsg(500102, "请求非法");
public static CodeMsg ACCESS_LIMIT_REACHED = new CodeMsg(500102, "访问频繁");
//登录模块 5002XX
public static CodeMsg SESSION_ERROR = new CodeMsg(500210, "Session不存在或者已经失效");
public static CodeMsg PASSWORD_EMPTY = new CodeMsg(500211, "登录密码不能为空");
public static CodeMsg MOBILE_EMPTY = new CodeMsg(500212, "手机号不能为空");
public static CodeMsg MOBILE_ERROR = new CodeMsg(500213, "手机号格式错误");
public static CodeMsg MOBILE_NOT_EXIST = new CodeMsg(500214, "手机号不存在");
public static CodeMsg PASSWORD_ERROR = new CodeMsg(500215, "密码错误");
//商品模块 5003XX
//订单模块 5004XX
public static CodeMsg ORDER_NOT_EXIST = new CodeMsg(500400, "订单不存在");
//秒杀模块 5005XX
public static CodeMsg MIAO_SHA_OVER = new CodeMsg(500500, "商品已经秒杀完毕");
public static CodeMsg REPEATE_MIAOSHA = new CodeMsg(500501, "不能重复秒杀");
public static CodeMsg MIAOSHA_FAIL = new CodeMsg(500502, "秒杀失败");
private CodeMsg(int i, String msg) {
this.code = i;
this.msg = msg;
}
private CodeMsg(){}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public CodeMsg fillArgs(Object... args){ //可变参数,Object数组
int code = this.code;
String message = String.format(this.msg,args);//msg有参数 %s "参数校验异常:%s"
return new CodeMsg(code,message);
}
// public static void main(String[] args) {
// String out1 = String.format("format结果: %s%s%s","厦门","福州","泉州");
// System.out.println(out1); //format结果: 厦门福州泉州
// }
@Override
public String toString() {
return "CodeMsg{" +
"code=" + code +
", msg='" + msg + '\'' +
'}';
}
}
1.3 Mybatis+Druid
导入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
配置application
#mybatis
mybatis.type-aliases-package=com.imooc.miaosha.domain
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.default-fetch-size=100
mybatis.configuration.default-statement-timeout=3000
mybatis.mapperLocations = classpath:com/imooc/miaosha/dao/*.xml
#druid
spring.datasource.url=jdbc:mysql://服务器或主机ip(ipv4):端口号(3306)/miaosha?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false
spring.datasource.username=**(改自己的)
spring.datasource.password=**
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
(感觉阿里云服务器不是很稳定,还是我自己的操作失误?安全组都设置好了,ping不通的时候,解决办法 重启 (╥╯^╰╥))
MySQL与类的对应关系
domain包下的类和数据库中的表是一一对应的
domain包下的类
navicat连接到mysql,查询的表
以user表(name,id)为例,有了domain包下的User类,在dao包下建立对应的IUserDao接口,在service包下建立UserService类实现接口方法,在controller包进行具体的操作。代码如下。
package com.imooc.miaosha.domain;
public class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.imooc.miaosha.dao;
@Mapper
public interface IUserDao {
@Select("SELECT * FROM user WHERE id = #{id}")
public User getById(@Param("id") int id);
@Insert("insert into user values (#{id},#{name})")
public int addUser(User user);
}
package com.imooc.miaosha.service;
@Service
public class UserService {
@Autowired
IUserDao userDao;
public User getById(int id){
return userDao.getById(id);
}
@Transactional //不加该注解,id=5的数据会成功插入不会回滚
public boolean tx(){
User u1 = new User();
u1.setId(5);
u1.setName("1111");
userDao.addUser(u1);
User u2 = new User();
u2.setId(2);
u2.setName("222222");
userDao.addUser(u2);
return true;
}
}
package com.imooc.miaosha.controller;
@Controller
@RequestMapping("/demo")
public class DemoController {
@Autowired
UserService userService;
//测试访问数据库
@RequestMapping("/db/get")
@ResponseBody
public Result<User> dbGet(){
User user = userService.getById(1);
return Result.success(user);
}
@RequestMapping("/db/tx")
public Result<Boolean> dbTx(){
userService.tx();
return Result.success(true);
}
}
1.4Jedis+Redis+通用缓存key
添加依赖+添加配置(application)
序列化用fastjson,把java对象转化成json字符串,写入redis中。为什么用fastjson而不用转化速度最快,效率最高的谷歌的Protocol Buffer ?因为Protocol Buffer 序列化之后是二进制的不方便直接读的文件,fastjson序列化后明文可读,方便查看,所以用fastjson。他们速度的区别,Protocol Buffer 1ms,Fastjson 2ms。
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.38</version>
</dependency>
#Redis
redis.host=*
redis.port=6379
redis.timeout=10
redis.password=*
redis.poolMaxTotal=1000 #连接池最大
redis.poolMaxIdle=1000 #最大空闲
redis.poolMaxWait=60000 #最大等待
借助Jedis连接处理redis数据
与application.properties 对应
@Component
@ConfigurationProperties(prefix = "redis")
public class RedisConfig {
private String host;
private int port;
private int timeout; //秒
private String password;
private int poolMaxTotal;
private int poolMaxIdle;
private int poolMaxWait;//秒
获取JedisPool的工厂
@Service
public class RedisPoolFactory {
@Autowired
RedisConfig redisConfig;
@Bean
public JedisPool JedisPoolFactory(){
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(redisConfig.getPoolMaxIdle());
poolConfig.setMaxTotal(redisConfig.getPoolMaxTotal());
poolConfig.setMaxWaitMillis(redisConfig.getPoolMaxWait()*1000); //这里设置的是毫秒值
JedisPool jp = new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(),
redisConfig.getTimeout()*1000, redisConfig.getPassword(), 0);
return jp;
}
}
通过JedisPool获取Jedis对象,操作Redis数据库
为了区分不同用户设置的相同key,如用户A,set key1,用户B,set key1,在redis存取前,增加了一个前缀prefix,变为"A:key1",“B:key1”。
@Service
public class RedisService {
@Autowired
JedisPool jedisPool;
/**
* 获取单个对象
* @param prefix
* @param key
* @param clazz
* @param <T>
* @return
*/
public <T> T get(IKeyPrefix prefix,String key,Class<T> clazz){
Jedis jedis = null;
try{
jedis = jedisPool.getResource();
//生成真正的key
String realKey = prefix.getPrefix() + key;
String str = jedis.get(realKey);
T t = stringToBean(str,clazz);
return t;
}finally {
returnToPool(jedis);
}
}
增加前缀,模板设计模式。
IKeyPrefix - KeyPrefix—(GoodsKey、UserKey、AccessKey、OrderKey、MiaoshaKey、MiaoshaUserKey)(这里key的设置并不跟数据表对应,根据需求设计)
package com.imooc.miaosha.redis;
public interface IKeyPrefix {
public int expireSeconds();
public String getPrefix();
}
package com.imooc.miaosha.redis;
public abstract class KeyPrefix implements IKeyPrefix { //抽象类不能直接创建实例
//通过KeyPrefix进行各个模块区分,保证每个prefix,expireseconds都是不同的
private int expireSeconds;
private String prefix;
public KeyPrefix(int expireSeconds,String prefix){
this.expireSeconds = expireSeconds;
this.prefix = prefix;
}
public KeyPrefix(String prefix){ // //默认0代表永不过期,public,可以被外面new,只是为了模拟方便,实际意义不大
this(0,prefix);
}
@Override
public int expireSeconds() {
return expireSeconds;
}
@Override
public String getPrefix() {
String className = getClass().getSimpleName();
return className+":"+prefix;
}
}
由Jedis+Redis联系JDBC和MySQL
JDBC是sun公司规定的Java与各种数据库连接的标准,由不同公司实现不同的驱动。使用步骤 如下
- 加载驱动
- 建立连接(DriverManager)
- 执行sql语句(statement、PrepareStatement(使用?占位符防止sql注入))
- 获得sql执行结果并封装结果集(ResultSet)
- 关闭资源、释放连接
一个数据库连接对象对应一个物理数据库连接,每次java对数据库进行操作时,都需要建立连接,使用完后关闭连接,频繁地打开、关闭连接造成系统性能低下,所以出现连接池技术,如C3P0、Druid。此外,Spring框架还重新封装了JDBC得到JDBCTemplate简化执行sql语句的步骤以及更方便的获取处理sql返回结果。
附:在linux上安装redis
在这里记录一下我自己服务器上各种软件安装目录
redis安装目录 :/www/server/redis
配置文件: /user/local/redis/redis.conf
日志文件: /user/local/redis/redis.log
redis-cli //进入redis命令行
auth 123456 //输入密码
? shutdown save //关闭并保存
redis安装使用的 linux命令记录
redis(6379)
tar -zvxf “name”.tar.gz //解压缩
mv “name” /新目录 //移动到新目录
cd /新目录
make -j 4 //编译
make install //将编译完成的文件添加到新目录中
redis.conf的参数修改
redis-server 启动
具体修改这俩参数:bind 0.0.0.0(整个网络)、daemonize yes //允许后台执行
修改密码的参数,requirepass 123456(密码)
命令 vi redis.conf //打开文件
命令 q //编辑文件
命令 :/string //查找文件内容
命令 :wq //保存文件退出
命令 ps - ef | grep redis //查看redis进程是否存在