Spring Boot学习笔记
Spring Boot是什么
默认配置了常见的框架,类似maven整合jar包一样。
为什么要用Spring Boot
- 开发简单
- 高效省时
怎么使用Spring Boot
创建项目
使用官网创建项目
-
访问官网: https://start.spring.io/
-
配置项目的相关信息,如项目类型、项目语言、Spring Boot版本、包名称等
-
下载项目的zip压缩包
-
在本机解压压缩包,使用编辑工具打开
IDEA示例如下:
- 打开菜单
2. 选择项目
3. 选择外部依赖
IDEA创建项目
-
打开菜单
-
选择项目类型
-
配置相关信息
-
选择添加的外部模块
-
选择项目保存路径,并创建项目
项目结构
项目创建好之后,结构为:
project
+- .mvn ———— mavn文件
|
+- src ———— 源代码目录
+- main ———— 项目源代码目录
+- java
+- com
+- example
+- myproject ———— 项目根目录
+- Application.java ———— 框架配置,执行入口
|
+- model ———— 实体类、dao层代码
| +- Customer.java
| +- CustomerRepository.java
|
+- service ———— 业务服务层代码
| +- CustomerService.java
|
+- controller ———— 视图层代码
| +- CustomerController.java
|
+- resources ———— 项目资源文件夹
+- application.properties ———— 框架配置文件,如端口
+- test ———— 测试源代码目录
+- java
+- target ———— 目标文件目录
|
+- .gitignore
|
+- demo.iml
|
+- HELP.md
|
+- mvnw
|
+- mvnw.cmd
|
+- pom.xml ———— maven配置文件
项目开发
Web项目开发
环境准备
-
创建spring boot项目
-
引入Web模块
在pom.xml文件中引入web项目,并刷新maven
<!-- 引入web模块 --> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
接口开发
创建类,添加控制器相关注解
package com.hutianyao.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 控制器
*/
@RestController
public class MainController {
@RequestMapping("/hello")
public String hello(){
return "hello";
}
@RequestMapping("/login")
public String login(){
return "登录成功!";
}
}
Filter开发
-
新建类,实现Filter接口及Filter方法
-
添加注解
- @Configuration 注解
添加在Filter类上,添加该注解后,此过滤器会自动加入到过滤器链中
配置文件
-
在 application.properties 中声明配置项
com.demo.name=test com.demo.description=这是一个Demo
-
在自定义配置类中使用配置项
package com.hutianyao.demo.properties; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; /** * 使用自定义配置 **/ @Component public class MainpProperties { @Value("${com.demo.name}") public String name; @Value("${com.demo.description") public String description; }
log配置
在application.properties 中配置
## 本机log地址
logging.path=/user/local/log
## 根据包名配置log级别
logging.level.com.favorites=DEBUG
logging.level.org.springframework.web=INFO
logging.level.org.hibernate=ERROR
使用Swagger
介绍:https://www.jianshu.com/p/349e130e40d5
使用:
https://blog.csdn.net/sanyaoxu_2/article/details/80555328
https://www.cnblogs.com/zhuhui-site/p/10092322.html
-
添加jar包
<properties> <springfox.version>2.9.2</springfox.version> </properties> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${springfox.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${springfox.version}</version> </dependency>
-
添加配置文件类
package com.swagger.demo.config; import io.swagger.annotations.ApiOperation; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.Date; /** * @description:swagger配置类 * @program: com.swagger.demo.config->SwaggerConfig * @author:hutianyao * @create: 2019-12-16 13:22 **/ @Configuration @EnableSwagger2 public class SwaggerConfig implements WebMvcConfigurer { /** * 创建api应用 * apiInfo():指定api信息 * select():返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现 * apis():指定api * paths(): * @return */ @Bean public Docket createRestApi(){ Docket docket = new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) .paths(PathSelectors.any()) .build() .directModelSubstitute(Date.class, String.class); return docket; } /** * 添加api基本信息 * 访问地址:http://项目地址/swagger-ui.html * @return apiInfo api信息 */ private ApiInfo apiInfo(){ ApiInfo apiInfo = new ApiInfoBuilder() .title("swagger-demo接口文档") .description("文档描述") .termsOfServiceUrl("www.baidu.com") .version("1.0.0") .build(); return apiInfo; } }
-
在web控制器层使用
package com.swagger.demo.web; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @description: * @program: com.swagger.demo.web->MainWeb * @author:hutianyao * @create: 2019-12-16 13:38 **/ @Api(tags = "登录注销") @RestController public class MainWeb { @GetMapping("/") @ApiOperation("首页") public String main(){ return "login"; } @GetMapping("/login") @ApiOperation("登录") public String login(@ApiParam(value = "用户名", required = true) String username){ return "index"; } @GetMapping("/logout") @ApiOperation("注销") public String logout(){ return "login"; } }
-
@Api()用于类;
表示标识这个类是swagger的资源 -
@ApiOperation()用于方法;
表示一个http请求的操作 -
@ApiParam()用于方法,参数,字段说明;
表示对参数的添加元数据(说明或是否必填等) -
@ApiModel()用于类
表示对类进行说明,用于参数用实体类接收 -
@ApiModelProperty()用于方法,字段
表示对model属性的说明或者数据操作更改
@ApiIgnore()用于类,方法,方法参数
表示这个方法或者类被忽略@ApiImplicitParam() 用于方法
表示单独的请求参数
@ApiImplicitParams() 用于方法包含多个 @ApiImplicitParam
-
使用Hibernate Validator
https://docs.jboss.org/hibernate/validator/4.2/reference/zh-CN/html_single/#validator-gettingstarted
https://www.cnblogs.com/mr-yang-localhost/p/7812038.html
https://www.cnblogs.com/sun-fan/p/10599038.html
-
导入jar包
spring-boot-starter-web 包里面有 hibernate-validator 包,不需要引用hibernate validator依赖。
-
常用注解
在实体类中(一般是针对画面的VO对象)添加注解:
在controller中添加注解:
参数前加入@Valid注解
使用JPA
-
添加jar包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
-
添加配置文件
在 application.properties 中进行配置
## 数据库连接 spring.datasource.url=jdbc:mysql://localhost:3306/test ## 数据库用户名 spring.datasource.username=root ## 数据库密码 spring.datasource.password=root ## 数据库驱动 spring.datasource.driver-class-name=com.mysql.jdbc.Driver ## 验证数据库表结构的级别 ## create: 每次加载 hibernate 时都会删除上一次的生成的表,然后根据你的 model 类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。 ## eate-drop :每次加载 hibernate 时根据 model 类生成表,但是 sessionFactory 一关闭,表就自动删除。 ## update:最常用的属性,第一次加载 hibernate 时根据 model 类会自动建立起表的结构(前提是先建立好数据库),以后加载 hibernate 时根据 model 类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等 应用第一次运行起来后才会。 ## validate :每次加载 hibernate 时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。 spring.jpa.properties.hibernate.hbm2ddl.auto=update ## 指定生成表名的存储引擎为 InnoDBD spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect ## 是否打印出自动生成的 SQL spring.jpa.show-sql= true
-
添加实体类
@Entity @Table(name = "user", schema = "demo") public class User implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Long id; @Column(nullable = false, unique = true) private String userName; @Column(nullable = false) private String passWord; @Column(nullable = false, unique = true) private String email; @Column(nullable = true, unique = true) private String nickName; @Column(nullable = false) private String regTime; //省略getter settet方法、构造方法 }
-
添加Dao
新建接口,并继承指定的接口
public interface UserRepository extends JpaRepository<User, Long> { User findByUserName(String userName); User findByUserNameOrEmail(String username, String email); }
使用Mybatis
使用:mybatis-spring-boot-starter
直接使用注解
-
导入相关jar包
<dependencies> <!-- SpringBoot Web模块 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> </dependencies>
-
添加配置
修改 application.yml配置文件
## 服务器 server: port: 10003 servlet: context-path: /mybatis-demo ## mybatis mapper文件路径 mybatis: type-aliases-package: com.example.mybatisdemo.mapper ## spring数据库配置 spring: datasource: url: jdbc:mysql://localhost:3306/demo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver
-
添加注解
在启动类中添加 @MapperScan 注解
//启动类注解 @SpringBootApplication //mybatis mapper文件位置注解 @MapperScan("com.example.mybatisdemo.mapper") public class MybatisAnnotationApplication { public static void main(String[] args) { SpringApplication.run(MybatisAnnotationApplication.class, args); } }
或者在每个mapper类上方加入@Mapper注解
-
开发mapper
package com.example.mybatisdemo.mapper; import com.example.mybatisdemo.model.User; import com.example.mybatisdemo.model.UserSex; import org.apache.ibatis.annotations.*; import java.util.List; /** * 用户Dao层接口 */ public interface UserMapper { /** * 查询所有用户 * @return */ @Select("select * from users") @Results({ @Result(property = "userSex", column = "user_sex", javaType = UserSex.class), @Result(property = "nickName", column = "nick_name") }) List<User> findAll(); /** * 根据id查找单个用户 * @param id 用户ID * @return 单个用户 */ @Select("select * from users u where u.id = #{id}") @Results({ @Result(property = "userSex", column = "user_sex", javaType = UserSex.class), @Result(property = "nickName", column = "nick_name") }) User findUserById(Long id); /** * 根据姓名查找用户 * @param userName 用户姓名 * @return 用户集合 */ @Select("select * from users u where u.userName like CONCAT('%', #{userName}, '%')") @Results({ @Result(property = "userSex", column = "user_sex", javaType = UserSex.class), @Result(property = "nickName", column = "nick_name") }) List<User> findUsersByUserName(String userName); /** * 新增用户 * @param user 用户对象 */ @Insert("insert into users(userName, nick_name, password, user_sex) values(#{userName}, #{nickName}, #{password}, #{userSex})") void insert(User user); /** * 修改用户 * @param user 用户对象 */ @Update("update users u set u.userName = #{userName}, u.nick_name = #{nickName}, user_sex = #{userSex} where u.id = #{id}") void update(User user); /** * 删除用户 * @param id 用户ID */ @Delete("delete from users u where u.id = #{id}") void delete(Long id); }
- @Results 结果集映射列表
- @Result 修饰返回的结果集,关联实体类属性和数据库字段一一对应,如果实体类属性和数据库属性名保持一致,就不需要这个属性来修饰。
- @Select 是查询类的注解,所有的查询均使用这个
- @Insert 插入数据库使用,直接传入实体类会自动解析属性到对应的值
- @Update 负责修改,也可以直接传入对象
- @delete 负责删除
-
{id} 构建的SQL中不包含“”
- ${id} 构建的SQL中包含“”
-
使用
注入mapper接口后直接使用
package com.example.mybatisdemo; import com.example.mybatisdemo.mapper.UserMapper; import com.example.mybatisdemo.model.User; import com.example.mybatisdemo.model.UserSex; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import javax.annotation.security.RunAs; import java.util.List; /** * @description:用户mapper测试 * @program: com.example.mybatisdemo->UserMapperTest * @author:admin * @create: 2019-11-13 15:36 **/ @RunAs("SpringRunner.class") @SpringBootTest public class UserMapperTest { //注入mapper接口 @Autowired private UserMapper userMapper; /** * 新增测试 * @throws Exception */ @Test public void testInsert() throws Exception{ userMapper.insert(new User("小张", "xiaozhang", "123456", UserSex.Man)); userMapper.insert(new User("小小张", "xiaoxiaozhang", "123456", UserSex.Man)); userMapper.insert(new User("小张一", "xiaozhangyi", "123456", UserSex.Woman)); System.out.println("保存成功!"); } /** * 查询测试 * @throws Exception */ @Test public void testQuery() throws Exception{ System.out.println("全部用户:"); List<User> users = userMapper.findAll(); for (User user : users) { System.out.println(user.getUserName()); } Long queryId = new Long(1); System.out.println(String.format("id为【%s】的用户:",queryId)); User user = userMapper.findUserById(queryId); if (user != null){ System.out.println(user.getUserName()); } else { System.out.println(String.format("未找到id为【%s】的用户,请确认id是否正确!", queryId)); } String queryName = new String("张"); System.out.println(String.format("name中包含【%s】的用户:", queryName)); users = userMapper.findUsersByUserName(queryName); for (User u : users) { System.out.println(u.getUserName()); } } /** * 删除测试 * @throws Exception */ @Test public void testDelete() throws Exception{ Long deleteId = new Long(9); userMapper.delete(deleteId); System.out.println(String.format("id为【%s】的用户删除成功!", deleteId)); } /** * 修改测试 * @throws Exception */ @Test public void testUpdate() throws Exception{ Long queryId = new Long(10); System.out.println(String.format("id为【%s】的用户:",queryId)); User user = userMapper.findUserById(queryId); if (user != null){ System.out.println("修改前:" + user.getUserName() + " " + user.getPassword() + " " + user.getNickName() + " " + user.getUserSex().getValue()); user.setUserName(user.getUserName() + "(修改)"); user.setNickName(user.getNickName() + "(修改)"); user.setPassword("987654"); user.setUserSex(UserSex.Man); userMapper.update(user); user = userMapper.findUserById(queryId); System.out.println("修改后:" + user.getUserName() + " " + user.getPassword() + " " + user.getNickName() + " " + user.getUserSex().getValue()); } else { System.out.println(String.format("未找到id为【%s】的用户,请确认id是否正确!", queryId)); } } }
使用XML配置文件
-
导入jar包
<dependencies> <!-- SpringBoot Web模块 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> </dependencies>
-
添加配置
修改 application.yml配置文件
## 服务器 server: port: 10003 servlet: context-path: /mybatis-demo ## mybatis配置 mybatis: ## mybatis配置文件路径 config-location: classpath:mybatis/mybatis-config.xml ## mapper文件路径 mapper-locations: classpath:mybatis/mapper/*.xml ## spring数据库数据源配置 spring: datasource: # url: jbdc:mysql://localhost:3306/demo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true url: jdbc:mysql://localhost:3306/demo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver
-
添加mybatis的配置文件(mybatis/mybatis-config.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> <typeAliases> <typeAlias alias="Integer" type="java.lang.Integer" /> <typeAlias alias="Long" type="java.lang.Long" /> <typeAlias alias="HashMap" type="java.util.HashMap" /> <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" /> <typeAlias alias="ArrayList" type="java.util.ArrayList" /> <typeAlias alias="LinkedList" type="java.util.LinkedList" /> </typeAliases> </configuration>
-
添加实体类映射文件(mybatis/mapper/UserMapper.xml)
每个实体类对应一个mapper文件,负责操作数据库
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <!-- 指定当前mapper文件对应的dao层接口 --> <mapper namespace="com.example.mybatisdemo.mapper.UserXmlMapper" > <!-- 配置返回数据集合,当前数据库表字段和Java对象属性之间的对应关系及对应的类型 --> <resultMap id="BaseResultMap" type="com.example.mybatisdemo.model.User" > <id column="id" property="id" jdbcType="BIGINT" /> <result column="userName" property="userName" jdbcType="VARCHAR" /> <result column="password" property="password" jdbcType="VARCHAR" /> <result column="user_sex" property="userSex" javaType="com.example.mybatisdemo.model.UserSex"/> <result column="nick_name" property="nickName" jdbcType="VARCHAR" /> </resultMap> <!-- 自定义SQL片段 --> <sql id="Base_Column_List" > id, userName, password, user_sex, nick_name </sql> <!-- 对应dao层接口中的一个方法,id和方法名一样,parameterType是传入参数的类型,resultMap是上方定义的返回对象集合 --> <select id="findAll" resultMap="BaseResultMap" > SELECT <!-- 使用上方的自定义SQL --> <include refid="Base_Column_List" /> FROM users </select> <select id="findUserById" parameterType="java.lang.Long" resultMap="BaseResultMap" > SELECT <include refid="Base_Column_List" /> FROM users WHERE id = #{id} </select> <select id="findUsersByUserName" parameterType="java.lang.String" resultMap="BaseResultMap" > SELECT <include refid="Base_Column_List" /> FROM users WHERE username like "%"#{userName}"%" </select> <insert id="insert" parameterType="com.example.mybatisdemo.model.User" > INSERT INTO users (userName, nick_name, password, user_sex) VALUES (#{userName}, #{nickName}, #{password}, #{userSex}) </insert> <update id="update" parameterType="com.example.mybatisdemo.model.User" > UPDATE users u SET <!-- 判断是否满足条件,条件满足时才添加语句 --> <if test="userName != null">u.userName = #{userName}, </if> <if test="nickName != null">u.nick_name = #{nickName}, </if> u.user_sex = #{userSex} WHERE u.id = #{id} </update> <delete id="delete" parameterType="java.lang.Long" > DELETE FROM users u WHERE u.id = #{id} </delete> </mapper>
-
添加dao层接口
创建dao层的接口,接口中的方法名称要和xml文件中的一致
package com.example.mybatisdemo.mapper; import com.example.mybatisdemo.model.User; import java.util.List; /** * @description:mybatis xml配置版dao层 * @program: com.example.mybatisdemo.mapper->UserXmlMapper * @author:admin * @create: 2019-11-13 16:59 **/ public interface UserXmlMapper { //查询所有用户 public List<User> findAll(); //根据id查找单个用户 public User findUserById(Long id); //根据name查找多个用户 public List<User> findUsersByUserName(String userName); //新增用户 public void insert(User user); //修改用户 public void update(User user); //删除用户 public void delete(Long id); }
-
使用
使用 @Autowired 注解直接引用dao层的接口,然后使用即可
package com.example.mybatisdemo; import com.example.mybatisdemo.mapper.UserMapper; import com.example.mybatisdemo.mapper.UserXmlMapper; import com.example.mybatisdemo.model.User; import com.example.mybatisdemo.model.UserSex; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import javax.annotation.security.RunAs; import java.util.List; /** * @description:用户mapper测试 * @program: com.example.mybatisdemo->UserMapperTest * @author:admin * @create: 2019-11-13 15:36 **/ @RunAs("SpringRunner.class") @SpringBootTest public class UserMapperTest { //注入mapper接口 @Autowired private UserXmlMapper userMapper; /** * 新增测试 * @throws Exception */ @Test public void testInsert() throws Exception{ userMapper.insert(new User("小张", "xiaozhang", "123456", UserSex.Man)); userMapper.insert(new User("小小张", "xiaoxiaozhang", "123456", UserSex.Man)); userMapper.insert(new User("小张一", "xiaozhangyi", "123456", UserSex.Woman)); System.out.println("保存成功!"); } /** * 查询测试 * @throws Exception */ @Test public void testQuery() throws Exception{ System.out.println("全部用户:"); List<User> users = userMapper.findAll(); for (User user : users) { System.out.println(user.getUserName()); } Long queryId = new Long(1); System.out.println(String.format("id为【%s】的用户:",queryId)); User user = userMapper.findUserById(queryId); if (user != null){ System.out.println(user.getUserName()); } else { System.out.println(String.format("未找到id为【%s】的用户,请确认id是否正确!", queryId)); } String queryName = new String("张"); System.out.println(String.format("name中包含【%s】的用户:", queryName)); users = userMapper.findUsersByUserName(queryName); for (User u : users) { System.out.println(u.getUserName()); } } /** * 删除测试 * @throws Exception */ @Test public void testDelete() throws Exception{ Long deleteId = new Long(16); userMapper.delete(deleteId); System.out.println(String.format("id为【%s】的用户删除成功!", deleteId)); } /** * 修改测试 * @throws Exception */ @Test public void testUpdate() throws Exception{ Long queryId = new Long(19); System.out.println(String.format("id为【%s】的用户:",queryId)); User user = userMapper.findUserById(queryId); if (user != null){ System.out.println("修改前:" + user.getUserName() + " " + user.getPassword() + " " + user.getNickName() + " " + user.getUserSex().getValue()); user.setUserName(user.getUserName() + "(修改)"); user.setNickName(user.getNickName() + "(修改)"); user.setPassword("987654"); user.setUserSex(UserSex.Man); userMapper.update(user); user = userMapper.findUserById(queryId); System.out.println("修改后:" + user.getUserName() + " " + user.getPassword() + " " + user.getNickName() + " " + user.getUserSex().getValue()); } else { System.out.println(String.format("未找到id为【%s】的用户,请确认id是否正确!", queryId)); } } }
Mybatis多数据源的处理
-
导入jar包
-
编辑spring boot的配置文件
在配置文件中配置多个数据源的连接信息
## 服务器配置 server: port: 10003 servlet: context-path: /mybatis-demo ## mybatis配置 mybatis: config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml ##spring数据源配置 spring: datasource: ## 配置第一个数据源 test1: jdbc-url: jdbc:mysql://localhost:3306/demo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver ## 配置第二个数据源 test2: jdbc-url: jdbc:mysql://localhost:3306/demo2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver
-
数据源配置
每个数据源都需要一个配置文件,配置这个数据源的信息、sqlSession、事务和SqlSessionTemplate
方法上的 @Primary 注解表示这个数据源是主数据源,多数据源时必须在一个数据源的配置类中添加这个注解来指明主数据源,不然会报错
package com.example.mybatisdemo.config; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScans; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import javax.sql.DataSource; /** * @description:第一个数据源配置 * @program: com.example.mybatisdemo.config->DataSource1Config * @author:admin * @create: 2019-11-14 09:58 **/ @Configuration //指定当前数据源mapper文件的路径和对应的SqlSessionTemplate名字 @MapperScan(basePackages = "com.example.mybatisdemo.mapper.test1", sqlSessionTemplateRef = "test1SqlSessionTemplate") public class DataSource1Config { /** * 创建DataSource * @return 数据源 */ @Bean(name = "test1DataSource") @ConfigurationProperties(prefix = "spring.datasource.test1") @Primary public DataSource testDataSource() { return DataSourceBuilder.create().build(); } /** * 创建SqlSessionFactory * @param dataSource 数据源 * @return SqlSessionFactory * @throws Exception */ @Bean(name = "test1SqlSessionFactory") @Primary public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/test1/*.xml")); return bean.getObject(); } /** * 创建事务 * @param dataSource 数据源 * @return DataSourceTransactionManager数据源事务管理 */ @Bean(name = "test1TransactionManager") @Primary public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } /** * 包装到test1SqlSessionTemplate(是线程安全的,负责管理MyBatis的SqlSession并且支持事务)中 * @param sqlSessionFactory * @return SqlSessionTemplate * @throws Exception */ @Bean(name = "test1SqlSessionTemplate") @Primary public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }
-
编写mapper的xml文件和到层接口
每个数据源的mapper文件和dao层的文件都放在一起,一个数据源有一个单独的mapper文件的目录和dao层目录,并且要在上面的数据源配置类中指定mapper文件包路径,如test1数据源有test1Mapper和test1Dao,test2数据源有test2Mapper和test2Dao
mapper文件和dao接口的写法和单数据源时是一样的
使用MongoDB
-
引入jar包
在pom.xml中添加
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> </dependencies>
-
添加配置文件
在 application.properties 中进行配置
## 单数据源(mongodb默认无密码,无密码时“用户名:密码@”可以省略) spring.data.mongodb.uri=mongodb://用户名:密码@IP地址:端口号/数据库 ## 示例 spring.data.mongodb.uri=mongodb://name:pass@localhost:27017/test ## 多数据源 ## 不同源同一个数据库 spring.data.mongodb.uri=mongodb://用户名:密码@ip1:port1,ip2:port2/数据库 ## 示例 spring.data.mongodb.uri=mongodb://user:pwd@ip1:port1,ip2:port2/database ## 同源不同数据库 mongodb. 777.uri=mongodb://192.168.0.75:20000 mongodb.primary.database=primary mongodb.secondary.uri=mongodb://192.168.0.75:20000 mongodb.secondary.database=secondary
-
创建数据实体类
public class User implements Serializable { private static final long serialVersionUID = -3258839839160856613L; private Long id; private String userName; private String passWord; //getter、setter省略 }
-
创建操作类
@Component public class UserRepositoryImpl implements UserRepository { @Autowired private MongoTemplate mongoTemplate; /** * 创建对象 * @param user */ @Override public void saveUser(User user) { mongoTemplate.save(user); } /** * 根据用户名查询对象 * @param userName * @return */ @Override public User findUserByUserName(String userName) { Query query=new Query(Criteria.where("userName").is(userName)); User user = mongoTemplate.findOne(query, User.class); return user; } /** * 更新对象 * @param user */ @Override public long updateUser(User user) { Query query=new Query(Criteria.where("id").is(user.getId())); Update update= new Update().set("userName", user.getUserName()) .set("passWord", user.getPassWord()); //更新查询返回结果集的第一条 UpdateResult result =mongoTemplate.updateFirst(query, update,User.class); //更新查询返回结果集的所有 // mongoTemplate.updateMulti(query,update,UserEntity.class); if(result!=null) return result.getMatchedCount(); else return 0; } /** * 删除对象 * @param id */ @Override public void deleteUserById(Long id) { Query query=new Query(Criteria.where("id").is(id)); mongoTemplate.remove(query,User.class); } }
-
使用
注入服务类,直接使用
使用Redis
-
引入jar包
在pom中引入
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
-
添加配置文件
在 application.properties 中进行配置
# Redis数据库索引(默认为0) spring.redis.database=0 # Redis服务器地址 spring.redis.host=localhost # Redis服务器连接端口 spring.redis.port=6379 # Redis服务器连接密码(默认为空) spring.redis.password= # 连接池最大连接数(使用负值表示没有限制) 默认 8 spring.redis.lettuce.pool.max-active=8 # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1 spring.redis.lettuce.pool.max-wait=-1 # 连接池中的最大空闲连接 默认 8 spring.redis.lettuce.pool.max-idle=8 # 连接池中的最小空闲连接 默认 0 spring.redis.lettuce.pool.min-idle=0
-
添加配置类
@Configuration //配置类 @EnableCaching //开启缓存 public class RedisConfig extends CachingConfigurerSupport{ @Bean public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); } }; } }
-
直接使用
@RunWith(SpringRunner.class) @SpringBootTest public class TestRedis { @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private RedisTemplate redisTemplate; @Test public void test() throws Exception { stringRedisTemplate.opsForValue().set("aaa", "111"); Assert.assertEquals("111", stringRedisTemplate.opsForValue().get("aaa")); } @Test public void testObj() throws Exception { User user=new User("aa@126.com", "aa", "aa123456", "aa","123"); ValueOperations<String, User> operations=redisTemplate.opsForValue(); operations.set("com.neox", user); operations.set("com.neo.f", user,1, TimeUnit.SECONDS); Thread.sleep(1000); //redisTemplate.delete("com.neo.f"); boolean exists=redisTemplate.hasKey("com.neo.f"); if(exists){ System.out.println("exists is true"); }else{ System.out.println("exists is false"); } // Assert.assertEquals("aa", operations.get("com.neo.f").getUserName()); } }
定时任务
-
添加jar包
添加 spring-boot-starter 包(项目创建完成后已经默认添加了)
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
-
配置启动类
在启动类上添加注解 @EnableScheduling
package com.example.schedulingdemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling public class SchedulingDemoApplication { public static void main(String[] args) { SpringApplication.run(SchedulingDemoApplication.class, args); } }
-
新建定时器类
新建一个普通类,类上添加 @Component 注解,定时器方法上添加 @Scheduled 注解
package com.example.schedulingdemo.task; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** * @description:定时任务1 * @program: com.example.schedulingdemo.task->SchedulerTask * @author:admin * @create: 2019-11-14 13:04 **/ @Component public class SchedulerTask { private int i = 1; private int j = 1; /** * 每六秒执行一次 */ @Scheduled(cron = "*/6 * * * * ?") private void processOne() { System.out.println("定时器【processOne】运行了【" + (i++) + "】次"); } /** * 每秒执行一次 */ @Scheduled(fixedDelay = 1000) public void processTwo(){ System.out.println("定时器【processTwo】运行了【" + (j++) + "】次"); } }
@Scheduled 注解的参数如下:
- cron :使用cron表达式配置定时器执行时间和间隔
- fixedRate :上一次开始执行后,间隔XX毫秒再次执行
- fixedDelay :上一次执行完成后,间隔XX毫秒开始执行
- initialDelay :第一次延迟XX毫秒开始执行
邮件服务
spring-boot-starter-mail
环境准备
-
导入jar包
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> </dependencies>
-
添加邮箱配置
## 服务器配置 server.port=10005 server.servlet.context-path=/mail-demo ## 邮件配置 ## 邮箱服务器地址 spring.mail.host=smtp.163.com ## 邮箱用户名 spring.mail.username=17608494096@163.com ## 邮箱密码 spring.mail.password=123456 ## 邮件格式 spring.mail.default-encoding=UTF-8 ## 发件人(以谁来发送邮件) mail.fromMail.addr=1760849496@163.com
发送邮件
文本邮件
-
编写邮件服务类
package com.example.maildemo.service.impl; import com.example.maildemo.service.MailService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.FileSystemResource; import org.springframework.mail.MailException; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Component; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import java.io.File; import java.nio.file.spi.FileSystemProvider; /** * @description:发送邮件 * @program: com.example.maildemo.service.impl->MailServiceImpl * @author:admin * @create: 2019-11-14 14:09 **/ @Component public class MailServiceImpl implements MailService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); //注入邮件发送类 @Autowired private JavaMailSender mailSender; //发件人 @Value("${mail.fromMail.addr}") private String from; /** * 发送邮件 * @param to 收件人 * @param title 邮件标题 * @param content 邮件正文 */ @Override public void sendSimpleMail(String to, String title, String content) { SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); simpleMailMessage.setFrom(from); simpleMailMessage.setTo(to); simpleMailMessage.setSubject(title); simpleMailMessage.setText(content); try { mailSender.send(simpleMailMessage); logger.info("邮件发送成功!"); } catch (MailException e) { logger.error("邮件发送失败!", e); } } }
-
发送邮件
package com.example.maildemo; import com.example.maildemo.service.MailService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class MailDemoApplicationTests { @Autowired private MailService mailService; @Test void contextLoads() { } /** * 邮件发送测试 * @throws Exception */ @Test public void testMail() throws Exception{ mailService.sendSimpleMail("734538662@qq.com","测试邮件","这是一个测试邮件。"); } }
HTML邮件
-
编写业务类
package com.example.maildemo.service.impl; import com.example.maildemo.service.MailService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.FileSystemResource; import org.springframework.mail.MailException; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Component; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import java.io.File; import java.nio.file.spi.FileSystemProvider; /** * @description:发送邮件 * @program: com.example.maildemo.service.impl->MailServiceImpl * @author:admin * @create: 2019-11-14 14:09 **/ @Component public class MailServiceImpl implements MailService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); //注入邮件发送类 @Autowired private JavaMailSender mailSender; //发件人 @Value("${mail.fromMail.addr}") private String from; /** * 发送HTML邮件 * @param to 收件人 * @param title 邮件标题 * @param content 邮件正文 */ @Override public void sendHtmlMail(String to, String title, String content) { MimeMessage message = mailSender.createMimeMessage(); try { MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(message, true); mimeMessageHelper.setFrom(from); mimeMessageHelper.setTo(to); mimeMessageHelper.setSubject(title); mimeMessageHelper.setText(content, true); mailSender.send(message); logger.info("HTML邮件发送成功!"); } catch (MessagingException e) { logger.error("HTML邮件发送失败!", e); } } }
-
发送邮件
package com.example.maildemo; import com.example.maildemo.service.MailService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class MailDemoApplicationTests { @Autowired private MailService mailService; @Test void contextLoads() { } /** * HTML邮件发送测试 * @throws Exception */ @Test public void testHTMLMail() throws Exception{ String htmlText = "<html><head><title>测试邮件</title></head>" + "<body><h1>邮件H1</h1><h3>邮件H3</h3><p>邮件p</p></body></html>"; mailService.sendHtmlMail("734538662@qq.com", "HTML测试邮件",htmlText); } }
附件邮件
-
编写业务类
package com.example.maildemo.service.impl; import com.example.maildemo.service.MailService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.FileSystemResource; import org.springframework.mail.MailException; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Component; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import java.io.File; import java.nio.file.spi.FileSystemProvider; /** * @description:发送邮件 * @program: com.example.maildemo.service.impl->MailServiceImpl * @author:admin * @create: 2019-11-14 14:09 **/ @Component public class MailServiceImpl implements MailService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); //注入邮件发送类 @Autowired private JavaMailSender mailSender; //发件人 @Value("${mail.fromMail.addr}") private String from; /** * 发送带附件的邮件 * @param to 收件人 * @param title 邮件标题 * @param content 邮件内容 * @param filePath 附件路径 */ @Override public void sendAttachmentsMail(String to, String title, String content, String filePath) { MimeMessage mimeMessage = mailSender.createMimeMessage(); try { MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true); mimeMessageHelper.setFrom(from); mimeMessageHelper.setTo(to); mimeMessageHelper.setSubject(title); mimeMessageHelper.setText(content, true); FileSystemResource fileSystemResource = new FileSystemResource(new File(filePath)); String fileName = filePath.substring(filePath.lastIndexOf(File.separator)); mimeMessageHelper.addAttachment(fileName, fileSystemResource); mailSender.send(mimeMessage); logger.info("附件邮件发送成功!"); } catch (MessagingException e) { logger.error("附件邮件发送失败!", e); } } }
-
发送邮件
package com.example.maildemo; import com.example.maildemo.service.MailService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class MailDemoApplicationTests { @Autowired private MailService mailService; @Test void contextLoads() { } /** * 附件邮件发送测试 * @throws Exception */ @Test public void testAttachmentsMail() throws Exception{ String filePath = "E:\\tmp\\1\\1\\1.md"; mailService.sendAttachmentsMail("734538662@qq.com", "附件邮件", "这是一个带附件的邮件", filePath); } }
静态资源邮件
-
编写业务类
package com.example.maildemo.service.impl; import com.example.maildemo.service.MailService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.FileSystemResource; import org.springframework.mail.MailException; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Component; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import java.io.File; import java.nio.file.spi.FileSystemProvider; /** * @description:发送邮件 * @program: com.example.maildemo.service.impl->MailServiceImpl * @author:admin * @create: 2019-11-14 14:09 **/ @Component public class MailServiceImpl implements MailService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); //注入邮件发送类 @Autowired private JavaMailSender mailSender; //发件人 @Value("${mail.fromMail.addr}") private String from; /** * 发送包含静态资源文件(图片)的邮件 * @param to 收件人 * @param title 邮件标题 * @param content 邮件内容 * @param rscPath 资源地址 * @param rscId 资源编号 */ @Override public void sendInlineResourceMail(String to, String title, String content, String rscPath, String rscId) { MimeMessage mimeMessage = mailSender.createMimeMessage(); try { MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true); mimeMessageHelper.setFrom(from); mimeMessageHelper.setTo(to); mimeMessageHelper.setSubject(title); mimeMessageHelper.setText(content, true); FileSystemResource fileSystemResource = new FileSystemResource(new File(rscPath)); mimeMessageHelper.addInline(rscId, fileSystemResource); mailSender.send(mimeMessage); logger.info("静态资源邮件发送成功!"); } catch (MessagingException e) { logger.error("静态资源邮件发送失败!", e); } } }
-
发送邮件
package com.example.maildemo; import com.example.maildemo.service.MailService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class MailDemoApplicationTests { @Autowired private MailService mailService; @Test void contextLoads() { } /** * 包含静态资源文件(图片)的邮件发送测试 * @throws Exception */ @Test public void testNlineResource() throws Exception{ String resId = "demo01"; String context = "<html><body>静态资源邮件测试!<img src=\'cid:" + resId + "\'></body></html>"; String resPath = "E:\\tmp\\笔记\\IDEA学习总结\\img\\29、项目热部署.png"; mailService.sendInlineResourceMail("734538662@qq.com", "静态资源邮件", context, resPath, resId); } }
邮件系统
邮件模板
-
导入 thymeleaf 的jar包
<!-- thymeleaf(邮件模板) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
-
创建模板文件
在 resorces/templates 下创建 html 模板文件
<!DOCTYPE html> <html lang="zh" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>验证邮箱</title> </head> <body> 尊敬的<label th:text="${u}">user</label>,您好: <br> 恭喜您成功注册本网站,即日起,您可享受网站的全部服务,感觉您的支持! </body> </html>
-
解析模板并发送
package com.example.maildemo; import ch.qos.logback.core.pattern.PostCompileProcessor; import com.example.maildemo.service.MailService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; @SpringBootTest class MailDemoApplicationTests { @Autowired private MailService mailService; @Autowired private TemplateEngine templateEngine; /** * 模板邮件发送测试 * @throws Exception */ @Test public void sendTemplateMail() throws Exception{ Context context = new Context(); context.setVariable("u", "张三先生"); String emailContent = templateEngine.process("emailTemplate", context); mailService.sendHtmlMail("2513762445@qq.com", "模板邮件测试", emailContent); } }
发送失败
-
接收到发送邮件请求,首先记录请求并且入库。
-
调用邮件发送接口发送邮件,并且将发送结果记录入库。
-
启动定时系统扫描时间段内,未发送成功并且重试次数小于3次的邮件,进行再次发送
异步发送
可以采用MQ发送邮件相关参数,监听到消息队列之后启动发送邮件。
使用Shiro
-
导入相关jar包
<!-- JPA --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- nekohtml --> <dependency> <groupId>net.sourceforge.nekohtml</groupId> <artifactId>nekohtml</artifactId> <version>1.9.22</version> </dependency> <!-- web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- test --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
-
创建实体类,编写DAO、Service层代码(根据用户名查询用户)
-
用户类
package com.shiro.demo.entity; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import javax.persistence.*; import java.io.Serializable; import java.util.List; /** * @description:用户信息 * @program: com.shiro.demo.entity->UserInfo * @author:hutianyao * @create: 2019-11-25 09:02 **/ @Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class UserInfo implements Serializable { //ID @Id @GeneratedValue private Integer uid; //帐号 @Column(unique =true) private String username; //名称(昵称或者真实姓名,不同系统不同定义) private String name; //密码; private String password; //加密密码的盐 private String salt; //用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定. private byte state; //立即从数据库中进行加载数据; @ManyToMany(fetch= FetchType.EAGER) // 一个用户具有多个角色 @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") }) private List<SysRole> roleList; /** * 密码盐. * 重新对盐重新进行了定义,用户名+salt,这样就更加不容易被破解 * @return */ public String getCredentialsSalt(){ return this.username+this.salt; } }
-
角色类
package com.shiro.demo.entity; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import javax.persistence.*; import java.util.List; /** * @description:角色信息 * @program: com.shiro.demo.entity->SysRole * @author:hutianyao * @create: 2019-11-25 09:12 **/ @Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class SysRole { // 编号 @Id@GeneratedValue private Integer id; // 角色标识程序中判断使用,如"admin",这个是唯一的: private String role; // 角色描述,UI界面显示使用 private String description; // 是否可用,如果不可用将不会添加给用户 private Boolean available = Boolean.FALSE; //角色 -- 权限关系:多对多关系; @ManyToMany(fetch= FetchType.EAGER) @JoinTable(name="SysRolePermission", joinColumns={@JoinColumn(name="roleId")}, inverseJoinColumns={@JoinColumn(name="permissionId")}) private List<SysPermission> permissions; // 用户 - 角色关系定义; // 一个角色对应多个用户 @ManyToMany @JoinTable(name="SysUserRole", joinColumns={@JoinColumn(name="roleId")}, inverseJoinColumns={@JoinColumn(name="uid")}) private List<UserInfo> userInfos; }
-
权限类
package com.shiro.demo.entity; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import javax.persistence.*; import java.util.List; /** * @description:权限信息 * @program: com.shiro.demo.entity->SysPermission * @author:hutianyao * @create: 2019-11-25 09:12 **/ @Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class SysPermission { //ID,主键. @Id@GeneratedValue private Integer id; //名称. private String name; //资源类型,[menu|button] @Column(columnDefinition="enum('menu','button')") private String resourceType; //资源路径. private String url; //权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view private String permission; //父编号 private Long parentId; //父编号列表 private String parentIds; // 是否可用,如果不可用将不会添加给用户 private Boolean available = Boolean.FALSE; //角色权限关系 @ManyToMany @JoinTable(name="SysRolePermission", joinColumns={@JoinColumn(name="permissionId")}, inverseJoinColumns={@JoinColumn(name="roleId")}) private List<SysRole> roles; }
-
Dao层代码(JPA)
package com.shiro.demo.dao; import com.shiro.demo.entity.UserInfo; import org.springframework.data.repository.CrudRepository; /** * @description:用户信息dao * @program: com.shiro.demo.dao->UserInfoDao * @author:hutianyao * @create: 2019-11-25 10:07 **/ public interface UserInfoDao extends CrudRepository<UserInfo,Long> { /** * 根据用户名查找用户 * @param userName * @return */ UserInfo findByUsername(String userName); }
-
Service层代码
接口:
package com.shiro.demo.service; import com.shiro.demo.entity.UserInfo; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * @description:用户 * @program: com.shiro.demo.service->userInfoService * @author:hutianyao * @create: 2019-11-25 09:48 **/ @Service public interface UserInfoService { /** * 根据用户名查找用户 * @param userName 用户名 * @return 用户 */ UserInfo findByUsername(String userName); }
实现类:
package com.shiro.demo.service.impl; import com.shiro.demo.dao.UserInfoDao; import com.shiro.demo.entity.UserInfo; import com.shiro.demo.service.UserInfoService; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * @description:用户信息 * @program: com.shiro.demo.service.impl->UserInfoServiceImpl * @author:hutianyao * @create: 2019-11-25 10:10 **/ @Service public class UserInfoServiceImpl implements UserInfoService { //注入用户Dao @Resource private UserInfoDao userInfoDao; /** * 根据用户名查找用户 * @param userName 用户名 * @return 用户 */ @Override public UserInfo findByUsername(String userName) { return userInfoDao.findByUsername(userName); } }
-
-
编写 Realm 类,继承 AuthorizingRealm 抽象类
-
登录认证( doGetAuthenticationInfo ):
- 获取用户登录时输入的用户名( authenticationToken.getPrincipal() )
- 使用自定义的服务层方法根据用户名查询用户
- 配置用户名、密码、盐、realm name信息( SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(用户名(用户对象), 密码(从数据库中获取的密码), 盐,. 当前 realm 名) )
-
权限控制( doGetAuthorizationInfo ):
-
获取用户信息( principalCollection.getPrimaryPrincipal() )
-
获取用户对应的角色信息列表
-
将角色信息添加到返回的 authorizationInfo 中(
单个:authorizationInfo.addRole(role.getRole())
多个: authorizationInfo.setRoles(roles)
)
-
获取角色对应的权限信息
-
将权限信息添加到返回的 authorizationInfo 中(
单个:authorizationInfo.addStringPermission(p.getPermission())
多个:authorizationInfo.setStringPermissions(stringPermissions)
)
-
package com.shiro.demo.shiro; import com.shiro.demo.entity.SysPermission; import com.shiro.demo.entity.SysRole; import com.shiro.demo.entity.UserInfo; import com.shiro.demo.service.UserInfoService; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import javax.annotation.Resource; /** * @description:用户Realm * @program: com.shiro.demo.shiro->UserRealm * @author:hutianyao * @create: 2019-11-25 09:39 **/ public class UserRealm extends AuthorizingRealm { //注入服务层对象 @Resource private UserInfoService userInfoService; /** * 登录认证 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("MyShiroRealm.doGetAuthenticationInfo()"); //获取用户的输入的账号. String username = (String)authenticationToken.getPrincipal(); System.out.println(authenticationToken.getCredentials()); //通过username从数据库中查找 User对象 //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 UserInfo userInfo = userInfoService.findByUsername(username); System.out.println("----->>userInfo="+userInfo); //未找到当前用户 if(userInfo == null){ return null; } //找到了当前用户 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( userInfo, //用户名 userInfo.getPassword(), //密码 ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt getName() //realm name ); return authenticationInfo; } /** * 权限控制 * * Shiro 的权限授权是通过继承AuthorizingRealm抽象类,重载doGetAuthorizationInfo(); * 当访问到页面的时候,链接配置了相应的权限或者 Shiro 标签才会执行此方法否则不会执行, * 所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回 null 即可。 * 在这个方法中主要是使用类:SimpleAuthorizationInfo进行角色的添加和权限的添加。 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()"); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); //获取用户信息 UserInfo userInfo = (UserInfo)principalCollection.getPrimaryPrincipal(); //获取用户对应的角色信息列表 for(SysRole role:userInfo.getRoleList()){ //添加单个角色 authorizationInfo.addRole(role.getRole()); //添加角色列表 // authorizationInfo.setRoles(roles); //获取当前角色对应的权限信息 for(SysPermission p:role.getPermissions()){ //添加单个权限信息 authorizationInfo.addStringPermission(p.getPermission()); //添加权限信息列表 // authorizationInfo.setStringPermissions(stringPermissions); } } return authorizationInfo; } }
-
-
编写配置类
package com.shiro.demo.config; import com.shiro.demo.shiro.UserRealm; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; /** * @description:Shieo过滤器 * @program: com.shiro.demo.config->ShiroConfig * @author:hutianyao * @create: 2019-11-25 09:30 **/ @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { System.out.println("ShiroConfiguration.shirFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); //拦截器. Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>(); //authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问 // 配置不会被拦截的链接 顺序判断 filterChainDefinitionMap.put("/static/**", "anon"); //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/logout", "logout"); //过滤链定义,从上向下顺序执行,一般将/**放在最为下边:这是一个坑,一不小心代码就不好使了; filterChainDefinitionMap.put("/**", "authc"); //设置登录页面,如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/login"); //登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/index"); //未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; } @Bean public UserRealm myShiroRealm(){ UserRealm myShiroRealm = new UserRealm(); return myShiroRealm; } }
使用JPA + Thymeleaf 进行CRUD
-
添加jar包
<!-- web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- jpa --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
-
添加配置
## 服务器配置 server.port=10008 server.servlet.context-path=/jtdemo ## 数据源配置 spring.datasource.url=jdbc:mysql://127.0.0.1/demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=true spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver ## jpa配置 spring.jpa.properties.hibernate.hbm2ddl.auto=create spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect spring.jpa.show-sql= true ## 关闭thymeleaf缓存 spring.thymeleaf.cache=false
-
修改启动类
package com.jtdemo.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; @SpringBootApplication public class JtDemoApplication extends SpringBootServletInitializer { /** * 添加 Servlet 支持 * @param application * @return */ @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(JtDemoApplication.class); } public static void main(String[] args) { SpringApplication.run(JtDemoApplication.class, args); } }
-
编写实体类,添加注解
package com.jtdemo.demo.entity; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; /** * @description:用户实体类 * @program: com.jtdemo.demo.entity->JTUser * @author:hutianyao * @create: 2019-11-25 14:19 **/ @Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class JTUser { @Id @GeneratedValue private long id; @Column(unique = true, nullable = false) private String userName; @Column(nullable = false) private String password; @Column(nullable = false) private int age; }
-
添加JPA的 Repository 接口
package com.jtdemo.demo.dao; import com.jtdemo.demo.entity.JTUser; import org.springframework.data.jpa.repository.JpaRepository; /** * @description:用户Dao * @program: com.jtdemo.demo.dao->JTUserDao * @author:hutianyao * @create: 2019-11-25 14:45 **/ public interface JTUserDao extends JpaRepository<JTUser, Long> { /** * 根据id查找用户 * @param id * @return */ JTUser findJTUserById(long id); /** * 根据用户名查询用户 * @param userName * @return */ JTUser findJTUserByUserName(String userName); /** * 根据id删除用户 * @param id * @return */ void deleteById(long id); }
-
编写业务层代码
service接口:
package com.jtdemo.demo.service; import com.jtdemo.demo.entity.JTUser; import org.springframework.stereotype.Service; import java.util.List; /** * @description:用户service * @program: com.jtdemo.demo.service->JTUserService * @author:hutianyao * @create: 2019-11-25 14:48 **/ @Service public interface JTUserService { /** * 查询所有用户 * @return */ List<JTUser> findAllJTUser(); /** * 根据用户编号查询用户 * @param id * @return */ JTUser findUserById(long id); /** * 根据用户姓名查找用户 * @param userName 用户姓名 * @return */ JTUser findUserByName(String userName); /** * 新增用户 * @param jtUser 新增用户对象 * @return */ JTUser addUser(JTUser jtUser); /** * 删除用户 * @param id 删除用户id * @return */ void deleteUserById(long id); /** * 更新用户 * @param jtUser 更新用户对象 * @return */ JTUser updateUser(JTUser jtUser); }
service实现类:
package com.jtdemo.demo.service.impl; import com.jtdemo.demo.dao.JTUserDao; import com.jtdemo.demo.entity.JTUser; import com.jtdemo.demo.service.JTUserService; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; /** * @description:用户实现类 * @program: com.jtdemo.demo.service.impl->JTUserServiceImpl * @author:hutianyao * @create: 2019-11-25 14:49 **/ @Service public class JTUserServiceImpl implements JTUserService { @Resource private JTUserDao jtUserDao; /** * 查询所有用户 * * @return */ @Override public List<JTUser> findAllJTUser() { return jtUserDao.findAll(); } /** * 根据用户编号查询用户 * * @param id * @return */ @Override public JTUser findUserById(long id) { return jtUserDao.findJTUserById(id); } /** * 根据用户姓名查找用户 * * @param userName 用户姓名 * @return */ @Override public JTUser findUserByName(String userName) { return jtUserDao.findJTUserByUserName(userName); } /** * 新增用户 * * @param jtUser 新增用户对象 * @return */ @Override public JTUser addUser(JTUser jtUser) { return jtUserDao.save(jtUser); } /** * 删除用户 * * @param id 删除用户id * @return */ @Override public void deleteUserById(long id) { jtUserDao.deleteById(id); } /** * 更新用户 * * @param jtUser 更新用户对象 * @return */ @Override public JTUser updateUser(JTUser jtUser) { return jtUserDao.save(jtUser); } }
-
编写 Controller 接口
package com.jtdemo.demo.web; import com.jtdemo.demo.entity.JTUser; import com.jtdemo.demo.service.JTUserService; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import javax.annotation.Resource; import java.util.List; /** * @description:用户控制器 * @program: com.jtdemo.demo.web->UserController * @author:hutianyao * @create: 2019-11-25 16:09 **/ @Controller public class JTUserController { /** * 注入服务层对象 */ @Resource private JTUserService jtUserService; /** * 首页 * @return */ @RequestMapping("/") public String index(){ return "redirect:/list"; } /** * 用户列表 * @param model * @return */ @RequestMapping("/list") public String list(Model model){ List<JTUser> jtUsers = jtUserService.findAllJTUser(); model.addAttribute("users", jtUsers); return "user/list"; } /** * 前往新增用户 * @return */ @RequestMapping("/toAdd") public String toAdd(){ return "user/userAdd"; } /** * 新增用户 * @param jtUser * @return */ @RequestMapping("/add") public String add(JTUser jtUser){ jtUserService.addUser(jtUser); return "redirect:/list"; } /** * 前往编辑用户 * @param model * @param id * @return */ @RequestMapping("/toEdit") public String toEdit(Model model, Long id){ JTUser jtUser = jtUserService.findUserById(id); model.addAttribute("user", jtUser); return "user/userEdit"; } /** * 编辑用户 * @param jtUser * @return */ @RequestMapping("/edit") public String edit(JTUser jtUser){ jtUserService.updateUser(jtUser); return "redirect:/list"; } /** * 删除用户 * @param id * @return */ @RequestMapping("/delete") public String delete(Long id){ jtUserService.deleteUserById(id); return "redirect:/list"; } }
-
使用 Thymeleaf 编写 HTML 文件
list.html:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"/> <title>userList</title> <!--<link rel="stylesheet" href="bootstrap-3.3.7-dist/css/bootstrap.min.css">--> <link rel="stylesheet" th:href="@{bootstrap-3.3.7-dist/css/bootstrap.min.css}"></link> </head> <body class="container"> <br/> <h1>用户列表</h1> <br/><br/> <div class="with:80%"> <table class="table table-hover"> <thead> <tr> <th>#</th> <th>User Name</th> <th>Password</th> <th>Age</th> <th>Edit</th> <th>Delete</th> </tr> </thead> <tbody> <tr th:each="user : ${users}"> <th scope="row" th:text="${user.id}">1</th> <td th:text="${user.userName}">neo</td> <td th:text="${user.password}">Otto</td> <td th:text="${user.age}">6</td> <td><a th:href="@{/toEdit(id=${user.id})}">edit</a></td> <td><a th:href="@{/delete(id=${user.id})}">delete</a></td> </tr> </tbody> </table> </div> <div class="form-group"> <div class="col-sm-2 control-label"> <a href="/toAdd" th:href="@{/toAdd}" class="btn btn-info">add</a> </div> </div> </body> </html>
userAdd.html:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"/> <title>user</title> <!--<link rel="stylesheet" href="bootstrap-3.3.7-dist/css/bootstrap.min.css">--> <link rel="stylesheet" th:href="@{bootstrap-3.3.7-dist/css/bootstrap.min.css}"></link> </head> <body class="container"> <br/> <h1>添加用户</h1> <br/><br/> <div class="with:80%"> <form class="form-horizontal" th:action="@{/add}" method="post"> <div class="form-group"> <label for="userName" class="col-sm-2 control-label">userName</label> <div class="col-sm-10"> <input type="text" class="form-control" name="userName" id="userName" placeholder="userName"/> </div> </div> <div class="form-group"> <label for="password" class="col-sm-2 control-label" >Password</label> <div class="col-sm-10"> <input type="password" class="form-control" name="password" id="password" placeholder="Password"/> </div> </div> <div class="form-group"> <label for="age" class="col-sm-2 control-label">age</label> <div class="col-sm-10"> <input type="text" class="form-control" name="age" id="age" placeholder="age"/> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <input type="submit" value="Submit" class="btn btn-info" /> <input type="reset" value="Reset" class="btn btn-info" /> </div> </div> </form> </div> </body> </html>
userEdit.html:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"/> <title>user</title> <!--<link rel="stylesheet" href="bootstrap-3.3.7-dist/css/bootstrap.min.css">--> <link rel="stylesheet" th:href="@{bootstrap-3.3.7-dist/css/bootstrap.min.css}"></link> </head> <body class="container"> <br/> <h1>修改用户</h1> <br/><br/> <div class="with:80%"> <form class="form-horizontal" th:action="@{/edit}" th:object="${user}" method="post"> <input type="hidden" name="id" th:value="*{id}" /> <div class="form-group"> <label for="userName" class="col-sm-2 control-label">userName</label> <div class="col-sm-10"> <input type="text" class="form-control" name="userName" id="userName" th:value="*{userName}" placeholder="userName"/> </div> </div> <div class="form-group"> <label for="password" class="col-sm-2 control-label" >Password</label> <div class="col-sm-10"> <input type="password" class="form-control" name="password" id="password" th:value="*{password}" placeholder="Password"/> </div> </div> <div class="form-group"> <label for="age" class="col-sm-2 control-label">age</label> <div class="col-sm-10"> <input type="text" class="form-control" name="age" id="age" th:value="*{age}" placeholder="age"/> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <input type="submit" value="Submit" class="btn btn-info" /> <a href="/toAdd" th:href="@{/list}" class="btn btn-info">Back</a> </div> </div> </form> </div> </body> </html>
上传文件
-
导入相关jar包
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.file</groupId> <artifactId>file-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>file-demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
-
编写配置文件
### 服务器配置 server.port = 10009 server.servlet.context-path = /file-demo ### 文件上传配置 #默认支持文件上传 spring.servlet.multipart.enabled = true #支持文件写入磁盘. spring.servlet.multipart.file-size-threshold = 0 # 上传文件的临时目录 spring.servlet.multipart.location = E://tmp/project/springboot/demo/file-demo/uploadFiles # 最大支持文件大小 spring.servlet.multipart.max-file-size = 102400 # 最大支持请求大小 spring.servlet.multipart.max-request-size = 102400
-
修改启动类
package com.file.filedemo; import org.apache.coyote.http11.AbstractHttp11Protocol; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.context.annotation.Bean; /** * 启动类 */ @SpringBootApplication public class FileDemoApplication { public static void main(String[] args) { SpringApplication.run(FileDemoApplication.class, args); } /** * 解决上传文件大于10M出现连接重置的问题 * @return */ @Bean public TomcatServletWebServerFactory tomcatEmbedded(){ TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory(); tomcatServletWebServerFactory.addConnectorCustomizers((TomcatConnectorCustomizer) connector -> { if ((connector.getProtocolHandler() instanceof AbstractHttp11Protocol<?>)){ //-1 means unlimited ((AbstractHttp11Protocol<?>) connector.getProtocolHandler()).setMaxSwallowSize(-1); } }); return tomcatServletWebServerFactory; } }
-
编写前端页面
templates/upload.html:
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <body> <h1>Spring Boot file upload example</h1> <form method="POST" action="/file-demo/upload" enctype="multipart/form-data"> <input type="file" name="file" /><br/><br/> <input type="submit" value="Submit" /> </form> </body> </html>
templates/uploadStatus.html:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <body> <h1>Spring Boot - Upload Status</h1> <div th:if="${message}"> <h2 th:text="${message}"/> </div> </body> </html>
-
编写控制器
package com.file.filedemo.web; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; /** * @description:文件控制器 * @program: com.file.filedemo.web->FileWeb * @author:hutianyao * @create: 2019-12-10 13:46 **/ @Controller public class FileWeb { /** * 访问项目,跳转到templates/upload.html页面 * @return */ @GetMapping("/") public String index() { return "upload"; } /** * 选择文件后,将文件上传到指定目录 * @param file * @param redirectAttributes * @return */ @PostMapping("/upload") public String singleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { if (file.isEmpty()) { redirectAttributes.addFlashAttribute("message", "Please select a file to upload"); return "redirect:/uploadStatus"; } try { // Get the file and save it somewhere byte[] bytes = file.getBytes(); Path path = Paths.get("E:\\tmp\\project\\springboot\\demo\\file-demo\\uploadFiles\\" + file.getOriginalFilename()); Files.write(path, bytes); redirectAttributes.addFlashAttribute("message", "You successfully uploaded '" + file.getOriginalFilename() + "'"); } catch (IOException e) { e.printStackTrace(); } return "redirect:/uploadStatus"; } /** * 文件上传完成,跳转到templates/uploadStatus.html页面 * @return */ @GetMapping("/uploadStatus") public String uploadStatus() { return "uploadStatus"; } }
-
编写异常处理控制器
package com.file.filedemo.web; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.mvc.support.RedirectAttributes; /** * @description:异常处理控制器 * @program: com.file.filedemo.web->GlobalExceptionHandler * @author:hutianyao * @create: 2019-12-10 13:56 **/ @ControllerAdvice//监控文件大小是否受限 public class GlobalExceptionHandler { @ExceptionHandler(MultipartException.class) public String handleError1(MultipartException e, RedirectAttributes redirectAttributes) { redirectAttributes.addFlashAttribute("message", e.getCause().getMessage()); return "redirect:/uploadStatus"; } }
上传文件到分布式文件系统FastDFS
https://blog.csdn.net/qq_34898847/article/details/82258453
https://blog.csdn.net/moshowgame/article/details/85341632
部署FastDFS服务端
下载安装包
libfastcommon:wget https://github.com/happyfish100/libfastcommon/archive/V1.0.42.tar.gz
fastdfs:wget https://github.com/happyfish100/fastdfs/archive/V6.04.tar.gz
fastdfs-nginx-module:wget https://github.com/happyfish100/fastdfs-nginx-module/archive/V1.22.tar.gz
nginx:wget http://nginx.org/download/nginx-1.17.6.tar.gz
安装tracker
-
编译安装包
-
libfastcommon
-
解压安装包到指定目录
cd /demo/surroundings/fastdfs/ tar -zxvf /demo/installationPackage/FastDFS/libfastcommon/V1.0.42.tar.gz
-
编译安装
cd libfastcommon-1.0.42/ ./make.sh ./make.sh install
-
-
fastdfs
-
解压到指定目录
cd /demo/surroundings/fastdfs/ tar -zxvf /demo/installationPackage/FastDFS/fastdfs/V6.04.tar.gz
-
-
编译安装
cd fastdfs-6.04/ ./make.sh ./make.sh install
-
查看文件
ll /etc/init.d/ |grep fdfs ll /etc/fdfs/ ll /usr/bin|grep fdfs
-
-
修改配置文件
-
创建数据目录文件夹
cd /demo/surroundings/fastdfs/ mkdir data cd data mkdir tracker
-
复制配置文件
cp /etc/fdfs/tracker.conf.sample /etc/fdfs/tracker.conf cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf
-
修改配置文件
tracker.conf:
# the tracker server port ### tracker server端口 port=20003 # the base path to store data and log files ### 数据和日志文件存储目录 base_path=/demo/surroundings/fastdfs/data/tracker # HTTP port on this tracker server http.server_port=20004
client.conf:
# the base path to store log files ### 数据和日志文件存储目录 base_path=/demo/surroundings/fastdfs/data/tracker # tracker_server can ocur more than once for multi tracker servers. # the value format of tracker_server is "HOST:PORT", # the HOST can be hostname or ip address, # and the HOST can be dual IPs or hostnames seperated by comma, # the dual IPS must be an inner (intranet) IP and an outer (extranet) IP, # or two different types of inner (intranet) IPs. # for example: 192.168.2.100,122.244.141.46:22122 # another eg.: 192.168.1.10,172.17.4.21:22122 ### 上面配置的tracker的地址和端口,如果单机则本地ip(不能127.0.0.1,最好是局域网ip),集群则中央tracker tracker_server=139.9.188.255:20003 #tracker_server=192.168.0.197:22122 #HTTP settings http.tracker_server_port=20005
-
-
启动 fdfs_trackerd
/etc/init.d/fdfs_trackerd start
安装storage
-
创建数据目录
cd /demo/surroundings/fastdfs/data mkdir storage mkdir data
-
复制配置文件
cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage.conf
-
修改配置文件
storage.conf:
# the storage server port ### storage server端口号 port=20006 # the base path to store data and log files # NOTE: the binlog files maybe are large, make sure # the base path has enough disk space ### 日志和文件存储路径 base_path=/demo/surroundings/fastdfs/data/storage # store_path#, based on 0, to configure the store paths to store file # if store_path0 not exists, it's value is base_path (NOT recommended) # the paths must be exist # # IMPORTANT NOTE: # the store paths' order is very important, don't mess up!!! # the base_path should be independent (different) of the store paths ### 存储路径,如果有多个就store_path0~store_pathN排列即可 store_path0=/demo/surroundings/fastdfs/data/data #store_path1=/home/yuqing/fastdfs2 # tracker_server can ocur more than once for multi tracker servers. # the value format of tracker_server is "HOST:PORT", # the HOST can be hostname or ip address, # and the HOST can be dual IPs or hostnames seperated by comma, # the dual IPS must be an inner (intranet) IP and an outer (extranet) IP, # or two different types of inner (intranet) IPs. # for example: 192.168.2.100,122.244.141.46:22122 # another eg.: 192.168.1.10,172.17.4.21:22122 ### tracker服务器地址,填写tracker_server=127.0.0.1:22122是错误的,不能为127.0.0.1,要填写局域网ip tracker_server=139.9.188.255:20003 #tracker_server=192.168.209.122:22122 # the port of the web server on this storage server ### http服务端口号,nginx监听的就是这个端口 http.server_port=20007
-
启动storage
/etc/init.d/fdfs_storaged start ss -lntup|grep 20006
测试上传
/usr/bin/fdfs_upload_file <config_file> <local_filename> [storage_ip:port] [store_path_index]
# 示例
/usr/bin/fdfs_upload_file /etc/fdfs/client.conf /demo/docs/hutianyao.cn .png
## 或
fdfs_test /demo/surroundings/FastDFS/conf/client.conf upload ~/caibh/test_images/XinXiJuZhiWang.jpg
安装fastdfs-nginx-module
-
创建文件目录
### nginx安装目录 cd /demo/surroundings/fastdfs/ mkdir nginx ### nginx数据目录 cd /demo/surroundings/fastdfs/data mkdir nginx
-
解压安装包
cd /demo/surroundings/fastdfs/nginx tar -zxvf /demo/installationPackage/FastDFS/fastdfs-nginx-module/V1.22.tar.gz
-
修改配置文件
cd fastdfs-nginx-module-1.22/src vim config
ngx_module_incs="/usr/include/fastdfs /usr/include/fastcommon/" CORE_INCS="$CORE_INCS /usr/include/fastdfs /usr/include/fastcommon/"
-
复制文件
cp src/mod_fastdfs.conf /etc/fdfs/mod_fastdfs.conf
-
修改文件
vim /etc/fdfs/mod_fastdfs.conf
# the base path to store log files base_path=/demo/surroundings/fastdfs/data/nginx # FastDFS tracker_server can ocur more than once, and tracker_server format is # "host:port", host can be hostname or ip address # valid only when load_fdfs_parameters_from_tracker is true ### tracker_server服务器地址,填写tracker_server=127.0.0.1:22122是错误的,不能为127.0.0.1,要填写局域网ip tracker_server=139.9.188.255:20003 # the port of the local storage server # the default value is 23000 ### storage_server地址 storage_server_port=20006 # the group name of the local storage server group_name=group1 # if the url / uri including the group name # set to false when uri like /M00/00/00/xxx # set to true when uri like ${group_name}/M00/00/00/xxx, such as group1/M00/xxx # default value is false url_have_group_name = true # store_path#, based 0, if store_path0 not exists, it's value is base_path # the paths must be exist # must same as storage.conf ### 文件地址需要和storage.conf文件中的store_path0一致 store_path0=/demo/surroundings/fastdfs/data/data #store_path1=/home/yuqing/fastdfs1
安装nginx
-
安装环境
yum install gcc gcc-c++ automake pcre pcre-devel zlib zlib-devel openssl openssl-devel -y
-
解压到指定目录
cd /demo/surroundings/fastdfs/nginx tar -zxvf /demo/installationPackage/nginx-1.17.6.tar.gz
-
添加fastdfs-nginx-module插件
./configure --prefix=/application/nginx/ --add-module=/demo/surroundings/fastdfs/nginx/fastdfs-nginx-module-1.22/src
-
编译源码
cd /demo/installationPackage/nginx-1.17.6/ make && make install
-
复制配置文件
cd fastdfs-6.04/ cp conf/http.conf /etc/fdfs/ cp conf/mime.types /etc/fdfs/
-
修改配置文件
vim /application/nginx/conf/nginx.conf
### 解决下载操作时报404的问题 user root; worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { ### 20007端口号与/etc/fdfs/storage.conf中的http.server_port=20007相对应 listen 20007; server_name localhost; ### torage对应有多个group的情况下,访问路径带group名称 location ~/group[0-9]/ { ngx_fastdfs_module; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
-
设置开机启动
cp /application/nginx/sbin/nginx /etc/init.d/
-
启动nginx
/etc/init.d/nginx
-
访问页面
wget http://139.9.188.255:20007/group1/M00/00/00/iwm8_13zOEyAa-Y1AARfkJSeKas700.png
在springboot中使用FastDFS
-
编译jar包
-
下载fastdfs-client-java
-
解压到指定目录
-
编译jar包
cd到解压代码根目录,运行命令:mvn clean install
-
-
导入相关jar包
项目测试
单元测试
-
导入相关jar包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
-
编写测试类
在test文件夹下创建包,再创建一个普通类,在类和方法上添加注解,直接运行方法即可
类上添加:
@RunWith(SpringRunner.class)
@SpringBootTest
方法上添加:
@Test
package com.example.mybatisdemo; /** * @description:用户mapper测试 * @program: com.example.mybatisdemo->UserMapperTest * @author:admin * @create: 2019-11-13 15:36 **/ @RunAs("SpringRunner.class") @SpringBootTest public class UserMapperTest { //注入mapper接口 // @Autowired // private UserMapper userMapper; @Autowired private UserXmlMapper userMapper; /** * 新增测试 * @throws Exception */ @Test public void testInsert() throws Exception{ userMapper.insert(new User("小张", "xiaozhang", "123456", UserSex.Man)); userMapper.insert(new User("小小张", "xiaoxiaozhang", "123456", UserSex.Man)); userMapper.insert(new User("小张一", "xiaozhangyi", "123456", UserSex.Woman)); System.out.println("保存成功!"); } }
注入MockMvc之后,可以测试控制器层接口
public class HelloControlerTests { private MockMvc mvc; //初始化执行 @Before public void setUp() throws Exception { mvc = MockMvcBuilders.standaloneSetup(new HelloController()).build(); } //验证controller是否正常响应并打印返回结果 @Test public void getHello() throws Exception { mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON)) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(MockMvcResultHandlers.print()) .andReturn(); } //验证controller是否正常响应并判断返回结果是否正确 @Test public void testHello() throws Exception { mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().string(equalTo("Hello World"))); } }
集成测试
热部署,在pom文件中添加:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
项目上线
jar包上线
maven
-
打包jar包
打包完成后 jar 包会生成到 target 目录下,命名一般是 项目名+版本号.jar
cd 项目根目录(和pom.xml同级) mvn clean package ## 或者执行下面的命令 ## 排除测试代码后进行打包 mvn clean package -Dmaven.test.skip=true
-
启动jar包
前台运行
java -jar target/spring-boot-scheduler-1.0.0.jar
后台运行
nohup java -jar target/spring-boot-scheduler-1.0.0.jar &
启动时读取指定的配置文件
java -jar app.jar --spring.profiles.active=dev
启动时设置jvm参数
java -Xms10m -Xmx80m -jar app.jar &
gradle
gradle build
java -jar build/libs/mymodule-0.0.1-SNAPSHOT.jar
war包上线
开发工具导出
命令行导出
maven
-
修改打包类型:pom.xml
<!-- 修改前 --> <packaging>jar</packaging> <!-- 修改后 --> <packaging>war</packaging>
-
排除Tomcat:pom.xml
<!-- 修改前 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 修改后 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency>
-
注册启动类
-
创建类【 ServletInitializer.java 】,并继承 SpringBootServletInitializer
-
覆盖 configure() 方法
-
把启动类 Application 注册进去
public class ServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure( SpringApplicationBuilder application) { return application.sources(Application.class); } }
-
-
项目根目录下执行命令
mvn clean package -Dmaven.test.skip=true
执行完成之后, 会在 target 目录下生成:项目名+版本号.war文件,拷贝到 tomcat 服务器中启动即可
Gradle
-
修改文件
... apply plugin: 'war' ... dependencies { compile("org.springframework.boot:spring-boot-starter-web:1.4.2.RELEASE"){ exclude mymodule:"spring-boot-starter-tomcat" } } ...
-
执行命令
gradle build
执行完成之后, war 包会生成在 build\libs 目录下
初始化数据
使用Jpa
- 设置 spring.jpa.hibernate.ddl-auto 的属性设置为 create 或者 create-drop
- 将sql文件放置在 resources 目录下,并重命名为:import.sql
- 在使用 spring boot jpa 的情况下设置 spring.jpa.hibernate.ddl-auto 的属性设置为 create 或 create-drop 的时候,Spring Boot 启动时默认会扫描 classpath 下面(项目中一般是 resources 目录)是否有 import.sql ,如果有机会执行 import.sql脚本
使用Spring JDBC
-
修改配置文件,添加:
spring: datasource: schema: 脚本中创建表的SQL文件路径 data: 脚本中初始化数据的SQL文件路径 sql-script-encoding: 设置脚本的编码 jpa: hibernate: ddl-auto: create/create-drop/update/validate/none ## 示例 spring: datasource: schema: classpath:db/schema.sql data: classpath:db/data.sql sql-script-encoding: utf-8 jpa: hibernate: ddl-auto: none
ddl-auto 属性说明:
-
create: 每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
-
create-drop :每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
-
update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据 model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。
-
validate :每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
-
none : 什么都不做。
-
-
SpringBoot启动时会自动执行指定的sql文件
自定义Favicon
-
关闭原有logo
在 application.propertie 文件中:
spring.mvc.favicon.enable=false
-
放置自己的logo
将自己的 favicon.ico 放到 src/main/resources/static 目录下
-
重新启动项目
使用 Jenkins 部署 Spring Boot 项目
环境安装和配置
-
安装和配置JDK
-
下载JDK
先在Windows中将jdk安装包下载好,在将安装包传输到Linux中
-
解压JDK
tar -zxvf ../../installationPackage/jdk-8u231-linux-x64.tar.gz
-
配置环境变量
JAVA_HOME=/demo/surroundings/jdk/jdk1.8.0_231 CLASSPATH=$JAVA_HOME/lib/ PATH=$PATH:$JAVA_HOME/bin export PATH JAVA_HOME CLASSPATH
-
-
安装和配置Maven
-
下载Maven
-
解压Maven
-
配置环境变量
MAVEN_HOME=/demo/surroundings/maven/apache-maven-3.6.2 export MAVEN_HOME export PATH=${PATH}:${MAVEN_HOME}/bin
-
-
安装和配置Git
- 直接安装
yum install git
-
源码安装
-
下载git源码
-
解压源码
tar -zxvf /demo/installationPackage/git-2.24.0.tar.gz
-
安装依赖
yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel gcc perl-ExtUtils-MakeMaker
-
删除旧版本的git
yum remove git
-
编译源码
cd git-2.24.0/ make prefix=/demo/surroundings/git all
-
安装到指定目录
make prefix=/demo/surroundings/git install
-
配置环境变量
vim /etc/profile ## 添加 PATH=$PATH:/demo/surroundings/git/bin export PATH ## 刷新配置 source /etc/profile
-
-
验证安装是否成功
git --version
防火墙配置
-
关闭防火墙
## 查看状态 firewall-cmd --state # 开启 service firewalld start # 重启 service firewalld restart # 关闭 service firewalld stop
-
安全组打开相应端口
安装和配置Jenkins
安装Jenkins
-
下载 Jenkins war包
-
配置 Jenkins 工作空间
vim /etc/profile export JENKINS_HOME=/demo/surroundings/jenkins/workspace source /etc/profile
-
启动 Jenkins 服务(初次启动时需记住打印的密码)
-
直接启动
在 Jenkins 的解压目录,执行命令:
java -jar jenkins.war --httpPort=10001
-
配置在Tomcat然后启动
将jenkins.war放置在webapps下,启动Tomcat(Tomcat安装目录下的bin目录执行 ./startup.sh )
-
-
浏览器访问 Jenkins 启动时设置的端口
http://139.9.188.255:10001/
-
使用初次启动时打印的密码登录,登录完成后选择安装官方推荐插件,安装出错时选择重试再次进行安装
-
按照提示创建管理员账号
配置Jenkins
-
安装插件:Git plugin 、 maven Integration plugin 、 publish over SSH
前往【系统管理】—【插件管理】—选择【可选插件】进行搜索,然后安装
-
配置全局变量
前往【系统管理】—【全局工具配置】进行全局变量配置
-
配置 JDK
-
配置 maven
-
配置git
-
其他配置
-
配置目标应用服务器
-
命令行配置
-
获取秘钥:在 jenkins 的 war包同级目录输入命令
ssh-keygen -t rsa
然后一直按回车,直到打印出方框,示例如下:
-
查看秘钥文件
cd ~/.ssh/ cat id_rsa cat id_rsa.pub
-
复制秘钥文件到目标应用服务器
使用 root 账户登录 jenkins 服务器
## 复制文件 ssh-copy-id -i id_rsa.pub 目标应用服务器ip地址 ## 设置权限 chmod 644 authorized_keys
注:使用 root 账户执行 ssh-copy-id -i ~/.ssh/id_rsa.pub 这个指令的时候如果需要输入密码则要配置sshd_config
vi /etc/ssh/sshd_config ## 配置 PermitRootLogin no ## 重启sshd服务 service sshd restart
-
在目标应用服务器上重启服务
service sshd restart
-
在 jenkins 服务器中免密码登录目标应用服务
-
-
图形页面配置
配置Push SSH
当 jenkins 和部署应用的服务器不是同一台服务器时配置
前往【系统管理】—【系统配置】
- Passphrase 不用设置
- Path to key 写上生成的ssh路径
- SSH Servers
- Name 随意起名代表这个服务,待会要根据它来选择
- Hostname 应用服务器的地址
- Username linux 登陆用户名
- Remote Directory 不填
部署项目
-
创建项目
-
配置项目构建规则
选择【丢弃旧的构建】
选择【参数化构建过程】并添加参数
-
jar_path:本意是准备项目打包后的jar位置,其实这里是Jenkins工作空间
-
spring_profile:这个是读取配置文件前缀,比如dev,test,prod
-
jar_name:jar包名称
-
project_name:项目名称
-
-
配置项目源码路径
-
配置构建环境
-
配置 build
## 除测试的包内容,使用后缀为 test 的配置文件 clean install -Dmaven.test.skip=true -Ptest
-
配置 Post Steps
当 jenkins 服务器和部署应用的服务器不同时,选择Send files or execute commands over SSH;
当 jenkins 服务器和部署应用的服务器相同时,选择Execute sell
-
使用Send files or execute commands over SSH:
- Source files(项目jar包名 ):target/xxx-0.0.1-SNAPSHOT.jar
- Remove prefix:target/
- Remote directory(代码应用服务器的目录地址):Jenkins-in/
- Exec command(应用服务器对应的脚本):Jenkins-in/xxx.sh
在应用服务器中新建sh(Exec command中指定的脚本文件):
DATE=$(date +%Y%m%d) export JAVA_HOME PATH CLASSPATH JAVA_HOME=/usr/java/jdk1.8.0_131 PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH CLASSPATH=.:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$CLASSPATH DIR=/root/xxx JARFILE=xxx-0.0.1-SNAPSHOT.jar if [ ! -d $DIR/backup ];then mkdir -p $DIR/backup fi cd $DIR ps -ef | grep $JARFILE | grep -v grep | awk '{print $2}' | xargs kill -9 mv $JARFILE backup/$JARFILE$DATE mv -f /root/Jenkins-in/$JARFILE . java -jar $JARFILE > out.log & if [ $? = 0 ];then sleep 30 tail -n 50 out.log fi cd backup/ ls -lt|awk 'NR>5{print $NF}'|xargs rm -rf
-
使用Execute sell:
在命令栏中输入:
mvn clean install -Dmaven.test.skip=true echo $spring_profile $jar_path $jar_name cd /usr/local/shell/ echo "【shell文件授权】开始授权shell文件..." chmod 777 startup.sh chmod 777 stop.sh echo "【shell文件授权】shell文件授权完成!" echo "【shop文件】开始执行stop.sh文件...(文件路径:/usr/local/shell/stop.sh)" ./stop.sh $jar_name echo "【shop文件】stop.sh文件执行成功!" echo "【startup文件】开始执行startup.sh文件...(文件路径:/usr/local/shell/startup.sh)" ./startup.sh $spring_profile $jar_path $jar_name $project_name echo "【startup文件】startup.sh文件执行成功!" echo "【jar包】正在启动项目..."
上面使用到的两个sh文件:
stop.sh:
jar_name=${1} echo "Stopping" ${jar_name} pid=`ps -ef | grep ${jar_name} | grep -v grep | awk '{print $2}'` if [ -n "$pid" ] then echo "kill -9 的pid:" $pid kill -9 $pid fi
startup.sh:
spring_profile=${1} jar_path=${2} jar_name=${3} project_name=${4} cd ${jar_path}/${project_name}/target/ echo "【startup文件】切换到jar包目录:" ${jar_path}/${project_name}/target/ echo "【startup文件】jar包授权开始..." chmod 777 ${jar_name} echo "【startup文件】jar包授权成功!" echo "【startup文件】jar包开始编译..." echo nohup java -jar ${jar_name} & BUILD_ID=dontKillMe nohup java -jar ${jar_name} --spring.profiles.active=${spring_profile} &
配置两个shell文件权限:
chmod 777 startup.sh chmod 777 stop.sh
-
-
访问项目
文件运行完成之后,即可访问项目
实施运维
查看JVM参数的值
jinfo -flags pid
-XX:CICompilerCount=3 -XX:InitialHeapSize=234881024 -XX:MaxHeapSize=3743416320 -XX:MaxNewSize=1247805440 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=78118912 -XX:OldSize=156762112 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
- -XX:CICompilerCount :最大的并行编译数
- -XX:InitialHeapSize 和 -XX:MaxHeapSize :指定 JVM 的初始和最大堆内存大小
- -XX:MaxNewSize : JVM 堆区域新生代内存的最大可分配大小
- …
- -XX:+UseParallelGC :垃圾回收使用 Parallel 收集器
重启
-
直接 kill 掉进程再次启动 jar 包
ps -ef|grep java ##拿到对应Java程序的pid kill -9 pid ## 再次重启 Java -jar xxxx.jar
-
脚本执行
-
配置pom.xml文件
-
Maven
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <executable>true</executable> </configuration> </plugin>
-
Gradle
springBoot { executable = true }
-
-
启动服务
-
直接启动
./yourapp.jar
-
注册服务
-
创建软链接指向jar包并加入到init.d中
ln -s /var/yourapp/yourapp.jar /etc/init.d/yourapp chmod +x /etc/init.d/yourapp
-
启动或停止应用
/etc/init.d/yourapp start|stop|restart ## 或者 service yourapp start|stop|restart
-
-
-
常用注解
持久层
JPA
-
@Entity
实体类注解,写在实体类上,表示这是一个实体类
-
@Table(name = "user", schema = "demo")
表名称注解,写在实体类上,配置这个实体类在数据库中对应的表名称和数据库名称,这里表示当前实体类对应 demo 数据库中的 user 表
-
@Id
主键标识,写在实体类的属性上面,表示这个属性在数据库中是主键
-
@GeneratedValue(strategy = GenerationType.IDENTITY)
主键生成策略配置,配置主键的生成策略,这里表示数据库自增长
-
@Column(nullable = false, unique = true)
数据库字段标识,写在实体类的属性上面,配置这个属性在数据库中的相关属性,如是否允许为空、是否唯一
-
@Transient
不映射到数据库注解,写在实体类的属性上面,标识这个属性不映射到数据库中,即不会保存到数据库中
-
@RepositoryDefinition
dao层接口注解,写在 dao 层的接口上面,表示这个接口继承了 Repository 接口
-
@Query("update AccountInfo a set a.salary = ?1 where a.salary < ?2")
dao层接口注解,写在 dao 层接口的方法上面,表示配置这个方法执行的SQL语句
-
@Modifying
dao层接口注解,写在 dao 层接口的方法上面,和 @Query 一起使用,表示这个方法是一个修改方法
-
@Transactional
dao层接口注解,写在 dao 层接口的方法上面,配置这个方法的事务类型
Mybatis
-
@MapperScan("com.example.mybatisdemo.mapper")
dao层接口文件路径配置,写在启动类(单数据源时)或者配置类(多数据源时)上,标识mybatis的dao层接口文件的路径
-
@Select("select * from users")
查询配置,写在 dao 层接口的方法上面,标识这个方法是个查询方法,并且配置这个方法执行的SQL语句
-
@Insert("insert into users(userName, nick_name, password, user_sex) values(#{userName}, #{nickName}, #{password}, #{userSex})")
新增配置,写在 dao 层接口的方法上面,标识这个方法是个新增方法,并且配置这个方法执行的SQL语句
-
@Update("update users u set u.userName = #{userName}, u.nick_name = #{nickName}, user_sex = #{userSex} where u.id = #{id}")
更新配置,写在 dao 层接口的方法上面,标识这个方法是个修改方法,并且配置这个方法执行的SQL语句
-
@Delete("delete from users u where u.id = #{id}")
删除配置,写在 dao 层接口的方法上面,标识这个方法是个删除方法,并且配置这个方法执行的SQL语句
-
@Results
结果集配置,写在 dao 层接口的方法上面,配置这个方法返回的结果集,内部接@Result注解表示单个结果,当数据库中字段的名称和实体类中属性的名称不相同是,使用它来讲字段和属性对应起来,当实体类中属性的类型是特殊的类型时,也使用它来表示类型
-
@Result(property = "userSex", column = "user_sex", javaType = UserSex.class)
返回结果映射配置,写在 dao 层接口的方法上面,配置这个方法返回的结果,当数据库中字段的名称和实体类中属性的名称不相同是,使用它来讲字段和属性对应起来,当实体类中属性的类型是特殊的类型时,也使用它来表示类型
-
@Bean(name = "test1DataSource")
注册bean对象,存在多数据源时,写在数据源配置类的方法上面,将该方法注册为一个指定名称的bean对象
-
@ConfigurationProperties(prefix = "spring.datasource.test1")
多数据源配置,存在多数据源时,写在数据源配置类的创建数据源方法上面,配置这个方法配置的数据源在配置文件中对应的对象,即将配置文件的指定数据源配置自动封装为一个数据源实体类
-
@Primary
主数据源配置,存在多数据源时,写在数据源配置类的方法上面,配置当前数据源是主数据源
业务层
-
@Autowired
注入服务,写在属性上面,表示注入这个类的一个对象
-
@Component
配置注解,添加在类上,表示当前类需要使用 properties 文件中定义的自定义配置时使用
-
@Value
配置注解,与 @Component 一起使用,添加在属性上,指定配置项的名称,即当前属性需要使用 properties 文件中的哪个配置项
-
@EnableScheduling
定时任务,添加在启动类上,标识当前程序使用到了定时任务
-
@Scheduled(cron = "*/6 * * * * ?")
定时任务,添加在定时任务的方法上,标识这是一个定时任务,并且配置这个定时任务的执行时间,这里表示每6秒执行一次
视图层
-
@Controller
控制器注解,添加在类上,标识当前类是一个控制器
-
@ResponseBody
让控制器返回json内容
-
@RestController
控制器注解,添加在类上,添加这个注解之后,这个类中的方法都会以json格式返回数据(不会返回HTML页面和JSP页面,只返回return里面的内容),相当于@Controller和@ResponseBody一起使用
-
@RequestMapping
映射路径注解,类和方法都可以添加,指定请求类或者方法的URL( value )、请求方式( method )、请求内容类型( consumes )、返回内容类型( produces )、请求必须包含参数( params )、请求必须包含头部( headers ),当类添加了这个注解之后,这个类中的方法都默认带上了类中设置的URL
-
@GetMapping 、@PostMapping、@PutMapping、@DeleteMapping
映射路径注解,设置类访问这个方法的请求方式和URL
-
@Configuration
添加在Filter类上,添加该注解后,此过滤器会自动加入到过滤器链中
其他
-
@SpringBootApplication
启动类标识,写在启动类上面,标识这是一个启动类,即程序的执行入口
-
@SpringBootTest
测试类标识,写在测试类上面,标识这是一个测试类
-
@Test
测试方法标识,写在测试类的方法上面,标识这是一个测试类的方法,可以直接运行
相关博客
- http://www.ityouknow.com/spring-boot.html
- 注解: https://blog.csdn.net/woshisunxinyuan/article/details/82779052
- 注解: https://blog.csdn.net/fenlin88l/article/details/89466723
Q&A
怎么修改项目的访问端口号和路径?
修改配置文件:demo\src\main\resources\application.properties,
## 项目访问端口号
server.port=10001
## 项目的默认访问URL
server.servlet.context-path=/demo
Redis和MongoDB的区别
Redis主要把数据存储在内存中,其“缓存”的性质远大于其“数据存储“的性质,其中数据的增删改查也只是像变量操作一样简单;
MongoDB却是一个“存储数据”的系统,增删改查可以添加很多条件,就像SQL数据库一样灵活。
指标 | MongoDB(v2.4.9) | Redis(v2.4.17) | 比较说明 |
---|---|---|---|
实现语言 | C++ | C/C++ | - |
协议 | BSON、自定义二进制 | 类Telnet | - |
性能 | 依赖内存,TPS较高 | 依赖内存,TPS非常高 | Redis优于MongoDB |
可操作性 | 丰富的数据表达、索引;最类似于关系数据库,支持丰富的查询语言 | 数据丰富,较少的IO | MongoDB优于Redis |
内存及存储 | 适合大数据量存储,依赖系统虚拟内存管理,采用镜像文件存储;内存占有率比较高,官方建议独立部署在64位系统(32位有最大2.5G文件限制,64位没有改限制) | Redis2.0后增加虚拟内存特性,突破物理内存限制;数据可以设置时效性,类似于memcache | 不同的应用角度看,各有优势 |
可用性 | 支持master-slave,replicaset(内部采用paxos选举算法,自动故障恢复),auto sharding机制,对客户端屏蔽了故障转移和切分机制 | 依赖客户端来实现分布式读写;主从复制时,每次从节点重新连接主节点都要依赖整个快照,无增量复制;不支持自动sharding,需要依赖程序设定一致hash机制 | MongoDB优于Redis;单点问题上,MongoDB应用简单,相对用户透明,Redis比较复杂,需要客户端主动解决。(MongoDB 一般会使用replica sets和sharding功能结合,replica sets侧重高可用性及高可靠性,而sharding侧重于性能、易扩展) |
可靠性 | 从1.8版本后,采用binlog方式(MySQL同样采用该方式)支持持久化,增加可靠性 | 依赖快照进行持久化;AOF增强可靠性;增强可靠性的同时,影响访问性能 | MongoDB优于Redis |
一致性 | 不支持事物,靠客户端自身保证 | 支持事物,比较弱,仅能保证事物中的操作按顺序执行 | Redis优于MongoDB |
数据分析 | 内置数据分析功能(mapreduce) | 不支持 | MongoDB优于Redis |
应用场景 | 海量数据的访问效率提升 | 较小数据量的性能及运算 | MongoDB优于Redis |
部署建议 | 建议集群部署,更多的考虑到集群方案 | 更偏重于进程顺序写入,虽然支持集群,也仅限于主-从模式 |
其他
Gradle
简介
类似于maven的项目构建工具,相比maven更加的简洁
项目管理工具(项目的构建、依赖管理、仓库管理)
- 一个像 ant 一样,通用的灵活的构建工具
- 一种可切换的,像 maven 一样的基于约定约定优于配置的构建框架
- 强大的多工程构建支持
- 强大的依赖管理(基于 ApacheIvy)
- 对已有的 maven 和 ivy 仓库的全面支持
- 支持传递性依赖管理,而不需要远程仓库或者 pom.xml 或者 ivy 配置文件
- ant 式的任务和构建是 gradle 的第一公民
- 基于 groovy,其 build 脚本使用 groovy dsl 编写
- 具有广泛的领域模型支持你的构建
优点
- 优化项目构建,能够构建出规范、灵活的项目
- 优化依赖管理
- 模块结构化构建
- 易于移植
下载
-
下载指定版本的Gradle https://services.gradle.org/distributions/
-
解压到指定的目录
-
配置环境变量
GRADLE_HOME:Gradle安装根目录中的bin目录
Path:%GRADLE_HOME%
-
测试是否安装成功
CMD中输入命令: gradle -v
打印出相关参数代表安装成功
使用
创建项目(SpringBoot)
使用编辑器或者在官网构建项目,构建项目时项目类型选择 gradle 即可
build.gradle 文件说明
使用gradle管理的项目全部在 build.gradle 文件(项目构建入口)中进行相应的声明和配置,gradle也提供了 gradle.properties 文件,可以将常用的属性配置在这个文件中(如jdk版本、编码类型等)
- 属性
- group :项目组织名
- name(artifact) :项目名
- version :项目版本号
- sourceCompatibility = 1.8 :项目 source 使用的 jdk 版本
- targetCompatibility = 1.8 :编译时使用的 jdk 版本或更新的java虚拟机兼容
- compileJava.options.encoding = 'UTF-8' :指定java的编码类型
- compileTestJava.options.encoding = 'UTF-8'
- 方法
- apply :应用插件
- dependencies :指定项目依赖包
- repositories :指定项目依赖的仓库
- task :定义任务
- buildscript : gradle脚本自身运行需要的资源
- allprojects :多项目构建时,所有项目都需要的资源(包括本项目)
- subprojects :所有子项目都需要的资源(不包括本项目)
- configurations
示例:
## gradle脚本运行所需的资源
buildscript {
repositories {
maven { url "http://repo2.maven.org/maven2" }
mavenLocal()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.8.RELEASE")
}
}
## 当前项目使用的插件
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'war'
apply plugin: 'idea'
## 当前项目war包的信息配置
war {
baseName = 'favorites-web'
version = '0.1.0'
}
## 当前项目使用的JDK版本
sourceCompatibility = 1.8
targetCompatibility = 1.8
## 当前项目使用的资源库
repositories {
mavenCentral()
maven { url "http://repo2.maven.org/maven2" }
}
## 当前项目使用的jar包
dependencies {
compile("org.springframework.boot:spring-boot-starter-web:1.5.8.RELEASE")
compile("org.springframework.boot:spring-boot-starter-thymeleaf:1.5.8.RELEASE")
compile("org.springframework.boot:spring-boot-starter-data-jpa:1.5.8.RELEASE")
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.6'
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.4'
compile("org.springframework.boot:spring-boot-devtools:1.5.8.RELEASE")
compile("org.springframework.boot:spring-boot-starter-test:1.5.8.RELEASE")
compile("org.springframework.boot:spring-boot-starter-mail:1.5.8.RELEASE")
compile 'org.webjars.bower:bootstrap:3.3.6'
compile("org.webjars:vue:1.0.24")
compile 'org.webjars.bower:vue-resource:0.7.0'
compile group: 'org.webjars', name: 'webjars-locator', version: '0.14'
compile group: 'org.jsoup', name: 'jsoup', version: '1.9.2'
compile 'org.springframework.boot:spring-boot-starter-tomcat'
}
bootRun {
addResources = true
}
springBoot {
mainClass = "com.favorites.Application"
}
博客
- build.gradle详解: https://blog.csdn.net/qq_36850813/article/details/93996333
- bulid.gradle详解: https://www.cnblogs.com/steffen/p/9212765.html
- build script 代码块: https://www.cnblogs.com/qiangxia/p/4826532.html
- 理论与实践: https://blog.csdn.net/u013700502/article/details/85231505
- 官方用户手册: https://docs.gradle.org/current/userguide/userguide.html
MongoDB
简介
一种 NoSQL (非关系型的数据库), MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。
官方文档: https://docs.mongodb.com/manual/
优点
- 高扩展性
- 分布式
- 低成本
- 架构灵活,半结构化数据
- 没有复杂的关系
安装
-
下载对应版本的 MongoDB
-
安装时选择自定义安装路径
配置
日志文件
在 MongoDB 的安装磁盘根目录下,创建 mongodb 文件夹,然后在 mongodb 中创建 data 文件夹,再在 data 文件夹中创建 log 文件夹作为日志文件目录
在log文件夹中创建 mongodb.log 文件,存储日志信息
数据文件
在 MongoDB 的安装磁盘根目录下,创建 mongodb 文件夹,然后在 mongodb 中创建 data 文件夹,再在 data 文件夹中创建 db 文件夹作为数据库文件目录
配置文件
在 MongoDB 的安装磁盘根目录下,创建 mongodb 文件夹,再在 mongodb 文件夹中创建 mongo.config 文件来配置MongoDB的信息
mongo.config文件示例:
##数据文件 此处=后对应到数据所存放的目录
dbpath=e:\mongodb\data\db
##日志文件 此处=后对应到日志文件所在路径
logpath=e:\mongodb\data\log\mongodb.log
##错误日志采用追加模式,配置这个选项后mongodb的日志会追加到现有的日志文件,而不是从新创建一个新文件
logappend=true
#启用日志文件,默认启用
journal=true
#这个选项可以过滤掉一些无用的日志信息,若需要调试使用请设置为false
quiet=true
#端口号 默认为27017
port=27017
验证是否安装成功
- 进入安装目录的bin文件夹下,点击mongod.exe,如果闪一下退出,说明安装正常
- 浏览器访问 localhost:27017
安装服务
- 已管理员身份运行CMD
- CD到MongoDB安装目录下的bin目录
- 执行目录: mongod --config E:\mongodb\mongo.config --install --serviceName "MongoDB" (--config后面是前面创建的MongoDB配置文件的路径)
配置环境变量
- 新建变量: MongoDB_HOME,值为MongoDB的安装路径
- 修改Path变量,在后面加上%MongoDB_HOME%
使用
-
启动服务
双击安装目录 bin 文件夹下的 mongod.exe即可启动服务
-
使用
- 命令行方式使用,直接在安装目录下双击打开 mongo.exe 文件即可
- 使用图形化界面
- 下载图形化工具
- Robo 3T: https://robomongo.org/
- MongoBooster : https://www.softpedia.com/get/Internet/Servers/Database-Utils/MongoBooster.shtml
- MongoDB Compass: https://www.mongodb.com/download-center/compass
- 使用图形化工具操作
- 下载图形化工具
配置密码
- 双击打开 mongo.exe
- 创建管理员账户:use admin
- 配置密码:db.createUser({user:"admin",pwd:"password",roles:["root"]})
- 认证登录:db.auth("admin", "password")
常用命令
数据库操作
-
db
显示当前的数据库名称
-
show dbs
显示当前服务器下数据库(非空的数据库)列表 -
use 数据库名
使用指定的数据库,如果数据库不存在,则创建数据库,如果已存在,则切换到数据库
如:use test
-
db.dropDatabase()
删除当前数据库
集合操作
-
show collections
显示当前数据库下所包含的集合(表)列表
-
db.createCollection('集合名')
创建一个空集合
如:db.createCollection('products')
-
db.集合名.drop()
把指定集合彻底从当前数据中删除,集合不再存在,注意与remove()的区别
如:db.products.drop()
-
db.集合名.remove({})
清空指定集合,remove()是用来删除数据的,数据删除之后集合还是存在的
如:db.students.remove({})
-
db.集合名.remove({条件key:条件value})
删除指定集合中条件key=条件value的数据。
注意:即使把集合中的所有数据都删除了,这个集合仍然存在, remove()是用来删除数据的,而drop()不仅会删除数据,还会把集合的结构给删除
如:db.products.remove({name:'abc'})
-
db.集合名.insert({name:'zhangsha'})
向指定集合中插入数据,如果集合存在,则直接插入数据,如果不存在,则先创建集合再插入数据
如:db.users.insert({name:'zhangsha'})
-
db.集合名.insert([{name:'lishi'},{name:'wangwu'}])
先指定集合中一次插入多个数据
如:db.products.insert([{name:'lishi'},{name:'wangwu'}])
-
db.集合名.update({条件字段key:条件字段value'},{$set:{修改字段key:修改字段value}},{
upsert: true,
multi:false
})把指定集合中条件key=条件value的那条数据的修改字段key设置成修改字段value,其它属性保留。
- $set是指更改的属性列表,不在列表中其他属性会被保留,如果不加此符号,其它属性会被丢弃(_id属性比较特殊,不会丢失)
- upsert:true:如果没有符号条件的更新时,则插入一条,为false时,则不会插入, 默认是false
- multi:false:一次只能更新一条数据,为true时,可更新多条,默认是false
如:
db.products.update({name:'华为手机'},{$set:{price:2000}},{
upsert: true,
multi:false
}) -
db.集合名.find()
查询指定集合中所有的数据
如:db.products.find()
-
db.集合名.find({ 字段key: {符号: 字段value}})
根据条件查询指定集合中的数据,符号有:
- $eq:等于,如:db.products.find({name:'苹果手机'}) 或 db.products.find({ name: {$eq: '苹果手机'}})
- $gt:大于 great,如:db.products.find({price:{$gt:18}})
- $gte:大于等于 great equal
- $lt:小于 less than
- $lte:小于等于 less than equal
- $ne:不等于 not equal
- $in:在范围内,如:db.products.find({name:{$in:['手机1','手机2']}})
- $nin:不在范围内
-
db.集合名.find({key:/^value/})
查找指定集合中key域中以value字符的开头的数据
如:db.products.find({name:/^华为/})
-
db.集合名.find({key:{$not:{$gt:value}}})
查询指定集合中key不满足value的数据,不存在key属性的数据也会查询出来
如:db.products.find({price:{$not:{$gt:100}}})
-
db.集合名.find(符号:[{key1:value1,key2:value2}])
多条件查询,符号有:
- $and:并且,可省略,不写符号默认为$and,如:db.products.find({name:"华为手机",price:800})
- $or:或者,如:db.products.find({$or:[{name:'华为手机'},{price:{$lt:1000}}]})
- $nor:not or 与$or相反, 格式同$or
-
db.集合名.find({域名称:{$exists: true}})
查询指定集合中包含指定域名称的数据
如:db.products.find({price:{$exists: true}})
-
db.集合名.find({字段名:{$type:2}})
查询指定集合中指定属性为字符串类型的数据
如:db.products.find({name:{$type:2}})
-
db.集合名.find({
$where: function(){
return this.字段名== 字段值
}
})查询指定集合中字段名=字段值的数据
如:
db.products.find({
$where: function(){
return this.name == '华为手机'
}
}) -
db.集合名.find({
$where: function(){
return this.字段名.indexOf(字段值) > -1;
}
})查询指定集合中指定域中包含指定字段值字符的数据
如:
db.products.find({
$where: function(){
return this.name.indexOf('华为手机') > -1;
}
}) -
db.集合名.distinct(key)
查询指定集合中不重复的key属性,返回的是数组
如:db.users.distinct('name')
-
db.集合名.count({key:value})
查询指定集合中key=value的数据数量
如:db.stu.count({name:'zhangshan'})
-
db.集合名.find().limit(5)
查询指定集合中前5条数据
如:db.stu.find().limit(5)
-
db.集合名.find().skip(5)
查询指定集合中跳过前5条后的数据
如:db.stu.find().skip(5)
-
db.集合名.find().sort({key:1})
查询指定集合中的全部数据,并按key属性正序排列 (1为正序, -1为倒序)
如:db.stu.find().sort({name:1})
由于mongodb的api接口方法很多,除以上命令外,其他的命令请多看官方文档
要求:根据官方文档中的方法原型,能够操作相应的方法
博客
https://www.cnblogs.com/mengyu/p/9071371.html
Shiro
简介
安全管理框架。
- Authentication(认证):用户身份识别,通常被称为用户“登录”
- Authorization(授权):访问控制。比如某个用户是否具有某个操作的使用权限。
- Session Management(会话管理):特定于用户的会话管理,甚至在非web 或 EJB 应用程序。
- Cryptography(加密):在对数据源使用加密算法加密的同时,保证易于使用。
- Subject:当前用户,Subject 可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它–当前和软件交互的任何事件。
- SecurityManager:管理所有Subject,SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞。
- Realms:用于进行权限信息的验证,我们自己实现。Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)。
优点
简单、易理解
使用
博客
https://www.cnblogs.com/dz-boss/p/9236083.html
Java枚举类
https://www.cnblogs.com/wjh-2017-07-15/p/7928240.html
定义
- 新建一个枚举类
- 定义枚举中的属性
- 定义枚举的构造方法
- 定义枚举的get方法
- 定义枚举对象
package com.hutianyao.demo.myEnum;
/**
* 自定义枚举类
*/
public enum Sex {
男(0, "男"), 女(1,"女"), 保密(3, "保密");
private int index;
private String name;
Sex(int index, String name){
this.index = index;
this.name = name;
}
public int getIndex(){
return index;
}
public String getName(){
return name;
}
}
使用
枚举类.枚举项
Sex.男;
Sex.女;
Sex.保密;
遍历枚举类
for(EnumTest e : EnumTest.values()) {
System.out.println(e.toString());
}
JPA
简介
sun公司定义的ORM(对象关系映射)规范,是一套接口,sun公司并没有实现它,目前主流的JPA框架有: Hibernate (JBoos)、EclipseTop(Eclipse社区)、OpenJPA (Apache基金会) ,其中Hibernate 的性能最好
优点
使用实体对象操作数据库,不用写SQL
使用(Spring Boot)
使用步骤
-
添加jar包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
-
添加配置文件
在 application.properties 中进行配置
## 数据库连接 spring.datasource.url=jdbc:mysql://localhost:3306/test ## 数据库用户名 spring.datasource.username=root ## 数据库密码 spring.datasource.password=root ## 数据库驱动 spring.datasource.driver-class-name=com.mysql.jdbc.Driver ## 验证数据库表结构的级别 ## create: 每次加载 hibernate 时都会删除上一次的生成的表,然后根据你的 model 类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。 ## eate-drop :每次加载 hibernate 时根据 model 类生成表,但是 sessionFactory 一关闭,表就自动删除。 ## update:最常用的属性,第一次加载 hibernate 时根据 model 类会自动建立起表的结构(前提是先建立好数据库),以后加载 hibernate 时根据 model 类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等 应用第一次运行起来后才会。 ## validate :每次加载 hibernate 时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。 spring.jpa.properties.hibernate.hbm2ddl.auto=update ## 指定生成表名的存储引擎为 InnoDBD spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect ## 是否打印出自动生成的 SQL spring.jpa.show-sql= true
-
添加实体类
实体类中使用 @Entity 注解,主键字段使用 @Id 注解标识、使用 @GeneratedValue 注解指定主键生成策略
@Entity @Table(name = "user", schema = "demo") public class User implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Long id; @Column(nullable = false, unique = true) private String userName; @Column(nullable = false) private String passWord; @Column(nullable = false, unique = true) private String email; @Column(nullable = true, unique = true) private String nickName; @Column(nullable = false) private String regTime; //省略getter settet方法、构造方法 }
-
添加Dao
新建接口,继承指定的接口,并设置实体类和主键类
public interface UserRepository extends JpaRepository<User, Long> { User findByUserName(String userName); User findByUserNameOrEmail(String username, String email); }
-
Repository 接口
没有显示任何的方法,需要自己声明方法
可以直接继承接口或者使用 @RepositoryDefinition 注解,并设置 domainClass 和 idClass
public interface UserDao extends Repository<AccountInfo, Long> { …… } @RepositoryDefinition(domainClass = AccountInfo.class, idClass = Long.class) public interface UserDao { …… }
-
CrudRepository 接口
包含实体类增删改查的一些接口,继承自Repository 接口
-
PagingAndSortingRepository 接口
包含实体类分页的接口,继承自CrudRepository 接口
-
JpaRepository 接口
包含其他功能接口,继承自PagingAndSortingRepository 接口
-
常用注解
实体类注解
-
@Entity 注解
实体类标识注解,标识这是一个实体类,可以指定映射到数据库中表的名称,不写默认为类的名称
@Entity //或 @Entity(name = "数据库表名称") // 示例 @Entity(name = "xwj_user")
-
@Table 注解
配置当前实体类在数据库中对应的表相关信息,它有三个属性,分别是
- name : 当前实体类在数据库中的表名称
- catalog :当前实体类所在数据库的目录(当前实体类对应的表在哪个数据库)
- schema :当前实体类所在数据库的模式、
@Table(name = "数据库表名称", schema = "数据库名称") //示例 @Table(name = "sys_user", schema = "demo")
-
@Id 注解
主键标识注解,标识当前属性是这个实体类的主键,一个实体类中必须有一个 @Id 注解
@Id
-
@IdClass 注解
引用外部类的联合主键,当一个类存在多个主键时,需要定义一个新的类,并使用这个注解指定联合主键类的名称
@Entity
@IdClass(ProjectId.class)
public class Project {
@Id int departmentId;
@Id long projectId;
}
//联合主键类
Class ProjectId {
int departmentId;
long projectId;
}
-
@GeneratedValue 注解
主键生成策略注解,配置主键的生成策略,包括 generator 和 strategy 两个属性
- generator属性:设置主键生成器的名称,当使用SEQUENCE 和Table 方式生成主键时,需要指定主键生成器的名称,主键生成器分别由 @SequenceGenerator和@TableGenerator 设置
- strategy属性:设置主键生成方式
- AUTO :由程序控制,默认
- IDENTITY :由数据库生成,采用数据库自增长, Oracle 不支持
- SEQUENCE :通过数据库序列生成,MySQL 不支持
- Table :提供特定的数据库生成,有利于数据库的移植
//数据库自动生成主键(数据库自增长) @GeneratedValue(strategy = GenerationType.IDENTITY) //根据指定的数据库表生成主键 @GeneratedValue(strategy = GenerationType.TABLE, generator = "roleSeq") @TableGenerator( name = "roleSeq", //主键生成器名称,和上面设置的名称对应 catalog = "", //主键生成器的数据库名称(schema属性相同) table = "seq_table", //主键生成器的表名称 pkColumnName = "seq_id", //主键生成器的字段名称(将表中的哪个字段作为主键) valueColumnName = "seq_count", //该主键当前的值 pkColumnValue = "", //主键生成器对应表的主键 initialValue = "", //主键初始值,默认为0 allocationSize = 1 //主键值每次增加的大小 ) //根据序列生成主键 @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "menuSeq") @SequenceGenerator( name = "menuSeq", //主键生成器名称 sequenceName = "MENU_SEQUENCE", //主键生成策略使用的序列名称 initialValue = 1, //主键初始值 allocationSize = 1 //主键值每次增加的大小 ) private Integer id;
-
@Basic 注解
当前属性是到数据库表字段的映射,可以不写,默认添加
-
@Transient 注解
当前属性不是到数据库表字段的映射,当前属性不会持久化到数据库中
-
@Column 注解
数据库列(字段注解),配置当前属性在数据库中的字段属性
-
@Enumerated 注解
直接映射枚举类型的字段,可设置按照枚举的下标或者名字来映射
//按照枚举的下标来映射 @Enumerated(EnumType.ORDINAL) //按照枚举的名字来 @Enumerated(EnumType.STRING)
-
@Temporal 注解
时间格式注解,配置映射到数据库中的时间格式
//获取年月日时分秒 yyyy-MM-dd HH:MM:SS @Temporal(TemporalType.TIMESTAMP) //获取年月日 yyyy-MM-dd @Temporal(TemporalType.DATE) //获取时分秒 HH:MM:SS @Temporal(TemporalType.TIME)
-
@Lob 注解
大数据类型注解,配置将该属性映射为数据库中的 text/blob/clob 类型,String 类的默认值 为 longtext
byte[] 、Image、File 默认值 为 longblob,最好搭配 @Basic(fetch=FetchType.LAZY) 实现延迟加载
@Lob
@Basic(fetch=FetchType.LAZY)
private Byte[] file;
DAO接口注解
-
@RepositoryDefinition 注解
表示当前接口继承自Repository,有 domainClass (实体类)和 idClass (主键类型)属性
@RepositoryDefinition(domainClass = AccountInfo.class, idClass = Long.class) public interface UserDao { }
-
@Query 注解
查询语句注解,配置当前方法执行的SQL语句,可通过循序或者名称传值
- 通过循序传值
public interface UserDao extends Repository<AccountInfo, Long> { @Query("select a from AccountInfo a where a.accountId = ?1") public AccountInfo findByAccountId(Long accountId); @Query("select a from AccountInfo a where a.balance > ?1") public Page<AccountInfo> findByBalanceGreaterThan( Integer balance, Pageable pageable); }
-
通过名称传值
public interface UserDao extends Repository<AccountInfo, Long> { public AccountInfo save(AccountInfo accountInfo); @Query("from AccountInfo a where a.accountId = :id") public AccountInfo findByAccountId(@Param("id")Long accountId); @Query("from AccountInfo a where a.balance > :balance") public Page<AccountInfo> findByBalanceGreaterThan( @Param("balance")Integer balance, Pageable pageable); }
-
@Modifying 注解
修改查询注解,和@Query配合使用,标识这个方法是一个修改查询
@Modifying
@Query("update AccountInfo a set a.salary = ?1 where a.salary < ?2")
public int increaseSalary(int after, int before);
- @Transactional 注解
事务类型注解,指定方法的事务类型
DAO方法关键字
-
And
等价于 SQL 中的 and 关键字,比如 findByUsernameAndPassword(String user, Striang pwd);
-
Or
等价于 SQL 中的 or 关键字,比如 findByUsernameOrAddress(String user, String addr);
-
Is, Equals
等价于 SQL 中的 = ,比如findByFirstnameIs(String firstname),findByFirstnameEquals(String firstname);
-
Between
等价于 SQL 中的 between 关键字(之间),比如 findBySalaryBetween (int max, int min);
-
LessThan
等价于 SQL 中的 "<",比如 findBySalaryLessThan(int max);
-
LessThanEqual
等价于 SQL 中的 "<="(小于等于),比如 findBySalaryLessThanEqual (int max);
-
GreaterThan
等价于 SQL 中的">",比如 findBySalaryGreaterThan(int min);
-
GreaterThanEqual
等价于 SQL 中的 ">="(大于等于),比如 findBySalaryGreaterThanEqual (int min);
-
After
等价于 SQL 中的">",比如 findByStartDateAfter (int min);
-
Before
等价于 SQL 中的 "<",比如 findByStartDateBefore (int max);
-
IsNull
等价于 SQL 中的 "is null",比如 findByUsernameIsNull();
-
IsNotNull
等价于 SQL 中的 "is not null",比如 findByUsernameIsNotNull();
-
NotNull
与 IsNotNull 等价;
-
Like
等价于 SQL 中的 "like",比如 findByUsernameLike(String user);
-
NotLike
等价于 SQL 中的 "not like",比如 findByUsernameNotLike(String user);
-
StartingWith
等价于 SQL 中的 “ parameter bound with appended ”(以...开始),比如 findByFirstnameStartingWith(String user, String s)
-
EndingWith
等价于 SQL 中的 “ parameter bound with prepended ”(以...结束),比如 findByFirstnameEndingWith (String user, String s)
-
Containing
等价于 SQL 中的 “ parameter bound wrapped ”(包含),比如 findByFirstnameContaining (String user, String s)
-
OrderBy
等价于 SQL 中的 "order by"(排序),比如 findByUsernameOrderBySalaryAsc(String user);
-
Not
等价于 SQL 中的 "!=",比如 findByUsernameNot(String user);
-
In
等价于 SQL 中的 "in",比如 findByUsernameIn(Collection
userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数; -
NotIn
等价于 SQL 中的 "not in",比如 findByUsernameNotIn(Collection
userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数; -
TRUE
等价于 SQL 中的 "true",比如 findByActiveTrue();
-
FALSE
等价于 SQL 中的 " false ",比如 findByActiveFALSE ();
-
IgnoreCase
等价于 SQL 中的 " UPPER "(忽略大小写),比如 findByFirstnameIgnoreCase (String user);
分页查询
查询时传入Pageable pageable参数即可,多参数时放在最后。
//查询方法
Page<User> findByUserName(String userName,Pageable pageable);
//查询
//指定排序规则
Sort sort = new Sort(Direction.DESC, "id");
//执行查询
Pageable pageable = new PageRequest(page, size, sort);
限制查询
查询指定个数的结果
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
自定义SQL查询
//修改查询
@Modifying
@Query("update User u set u.userName = ?1 where u.id = ?2")
int modifyByIdAndUserId(String userName, Long id);
//删除查询
@Transactional
@Modifying
@Query("delete from User where id = ?1")
void deleteByUserId(Long id);
//加锁查询
@Transactional(timeout = 10)
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
多表查询
-
创建结果集接口
将多表查询出来的属性放到一个接口中
public interface HotelSummary { City getCity(); String getName(); Double getAverageRating(); default Integer getAverageRatingRounded() { return getAverageRating() == null ? null : (int) Math.round(getAverageRating()); } }
-
设置返回类型为新创建的接口
设置dao中返回的数据类型为结果集接口
@Query("select h.city as city, h.name as name, avg(r.rating) as averageRating " - "from Hotel h left outer join h.reviews r where h.city = ?1 group by h") Page<HotelSummary> findByCity(City city, Pageable pageable); @Query("select h.name as name, avg(r.rating) as averageRating " - "from Hotel h left outer join h.reviews r group by h") Page<HotelSummary> findByCity(Pageable pageable);
-
使用查询
使用结果集集合接收返回的数据
//接收查询的数据 Page<HotelSummary> hotels = this.hotelRepository.findByCity(new PageRequest(0, 10, Direction.ASC, "name")); //出来查询数据 for(HotelSummary summay:hotels){ System.out.println("Name" +summay.getName()); }
多数据源支持
同构数据库
相同类型的数据库,但存在多个数据库
- 配置多数据源
- 不同源的实体类放入不同包路径
- 声明不同的包路径下使用不同的数据源、事务支持
异构数据库
不同类型的数据库,并且存在多个数据库
-
一个实体类只使用了一种数据库
关系型数据库使用 @Entity 注解
Mongodb 类型使用 @Document 注解
不同的数据源使用不同的实体就可以了
interface PersonRepository extends Repository<Person, Long> { … } @Entity public class Person { … } interface UserRepository extends Repository<User, Long> { … } @Document public class User { … }
-
一个实体类使用了多种数据库
- 同时使用 @Entity 和 @Document 注解
interface JpaPersonRepository extends Repository<Person, Long> { … } interface MongoDBPersonRepository extends Repository<Person, Long> { … } @Entity @Document public class Person { … }
- 通过对不同的包路径进行声明,比如 A 包路径下使用 mysql,B 包路径下使用 MongoDB
@EnableJpaRepositories(basePackages = "com.neo.repositories.jpa") @EnableMongoRepositories(basePackages = "com.neo.repositories.mongo") interface Configuration { }
博客
https://www.jianshu.com/p/6d38a6c561f8
简介: https://blog.csdn.net/OrangeChenZ/article/details/84986648
多表CRUD: https://blog.csdn.net/woshisunxinyuan/article/details/82779052
Spring Data JPA https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-jpa/
Thymeleaf
简介
模板渲染引擎
优点
能够直接使用浏览器展示内容,不用启动整个项目,也可以在没有网络的情况下运行