简

人生短暂,学海无边,而大道至简。


  • 首页

  • 归档

  • 分类

  • 标签

MyCat集群环境负载均衡

发表于 2018-02-20 | 分类于 mysql

MyCat集群环境负载均衡

1、配置数据库集群

所有的集群配置,都必须配置多主多从模式。即多个 master 节点相互之间配置主从。如:master1 和 slave1 为第一组主从,master2 和 slave2 为第二组主从,master1 和 master2互为对方的主/从。双主双从结构。

20200420191848

注意:

crc32slot 分片规则,在使用的时候,要求必须先设置好分片规则,再启动 mycat。
如果先启动了 mycat,再设置分片规则,会导致分片规则失效。需要删除 conf 目录中的ruledata 子目录。ruledata 目录中会记录 crc32slot 的分片节点,日志文件命名规则为crc32slot_表名。

2、集群负载均衡策略

第一种方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100">
<table name="t_user" dataNode="dn1,dn2,dn3" rule="crc32slot" />
</schema>
<dataNode name="dn1" dataHost="localhost1" database="db1" />
<dataNode name="dn2" dataHost="localhost1" database="db2" />
<dataNode name="dn3" dataHost="localhost1" database="db3" />
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="192.168.1.102:3306" user="root" password="root">
<readHost host="hostS1" url="192.168.1.103:3306" user="root" password="root" />
</writeHost>
<writeHost host="hostM2" url="192.168.1.104:3306" user="root" password="root">
<readHost host="hostS2" url="192.168.1.105:3306" user="root" password="root" />
</writeHost>
</dataHost>
</mycat:schema>

这种方式有缺陷:可能有 IO 延迟问题。

第二种方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100">
<table name="t_user" dataNode="dn$1-6" rule="crc32slot" />
</schema>
<dataNode name="dn1" dataHost="localhost1" database="db1" />
<dataNode name="dn2" dataHost="localhost1" database="db2" />
<dataNode name="dn3" dataHost="localhost1" database="db3" />
<dataNode name="dn4" dataHost="localhost2" database="db1" />
<dataNode name="dn5" dataHost="localhost2" database="db2" />
<dataNode name="dn6" dataHost="localhost2" database="db3" />
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="2" slaveThreshold="100">
<heartbeat>show slave status</heartbeat>
<writeHost host="hostM1" url="192.168.1.102:3306" user="root" password="root"/>
<writeHost host="hostS2" url="192.168.1.103:3306" user="root" password="root" />
</dataHost>
<dataHost name="localhost2" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="2" slaveThreshold="100">
<heartbeat>show slave status</heartbeat>
<writeHost host="hostM2" url="192.168.1.104:3306" user="root" password="root"/>
<writeHost host="hostS2" url="192.168.1.105:3306" user="root" password="root" />
</dataHost>
</mycat:schema>

建议为不同的表格定位不同的 dataHost 节点

balance 属性

balance="0", 不开启读写分离机制,所有读操作都发送到当前可用的 writeHost 上
balance="1",全部的 readHost 与 stand by writeHost 参与 select 语句的负载均衡
balance="2",所有读操作都随机的在 writeHost、 readhost 上分发。
balance="3", 所有读请求随机的分发到 writeHost 对应的 readhost 执行,writerHost不负担读压力

writeType 属性

writeType="0", 所有写操作发送到配置的第一个 writeHost,第一个挂了切到还生存的第二个 writeHost,重新启动后已切换后的为准,切换记录在配置文件中:conf/dnindex.properties(datanode index)
writeType="1",所有写操作都随机的发送到配置的 writeHost,1.5 以后废弃不推荐

switchType 属性:涉及到读写分离问题,可以解决 IO 延迟问题。

switchType='-1' 表示不自动切换
switchType='1' 默认值,表示自动切换
switchType='2' 基于 MySQL 主从同步的状态决定是否切换读写主机,心跳语句为 show slave status。 如果写数据通过心跳检测出那台是master和slave,就往master上写数据;如果是读,通过心跳检测IO延迟,如果没有就在slave上读,如果有延迟,则在master上读。

注意: 在 mycat 中,rule.xml 配置文件中定义的分片规则只能给一个表格使用。如果有多个表格使用同一个分片规则,需要再 rule.xml 配置文件中,为每个表格定义一个分片规则。

配置mycat eye可视化监控界面https://blog.csdn.net/zh15732621679/article/details/78837760

FreeMarker-在spring mvc上使用

发表于 2018-02-15 | 分类于 前端

Maven配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hu</groupId>
<artifactId>maven-freemarker-springmvc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.20</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>3.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.2.9.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>801</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
<!-- 配置编译文件 -->
<resources>
<!-- src/main/java 里面的资源文件进行编译到classes 中 -->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<!-- 上面修改了默认配置,打破了规则,也要配置把resources中的资源文件让其被编译到class -->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
</project>

Web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

Spring MVC配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<context:component-scan base-package="com.hu" />

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />

<!-- 配置Freemarker属性文件路径 -->
<bean id="freemarkerConfiguration" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="classpath:freemarker.properties" />
</bean>
<!-- 配置freeMarker模板 -->
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<!-- 视图解析器在/WEB-INF/ftl/路径下扫描视图文件 -->
<property name="templateLoaderPath" value="/WEB-INF/ftl/" />
<property name="freemarkerSettings">
<props>
<prop key="template_update_delay">0</prop>
<prop key="default_encoding">UTF-8</prop>
<prop key="number_format">0.##########</prop>
<prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
<prop key="classic_compatible">true</prop>
<prop key="template_exception_handler">ignore</prop>
</props>
</property>
</bean>
<!-- 配置freeMarker视图解析器 -->
<bean id="freemakerViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<!-- 扫描路径內所有以ftl結尾的文件 -->
<property name="suffix" value=".ftl"/>
<!-- 配置freemarker 可以在 freemarker.properties 中配置,如果在这里配置,则不用freemarker.properties-->
<property name="contentType" value="text/html; charset=UTF-8" />
<property name="exposeRequestAttributes" value="true" />
<property name="exposeSessionAttributes" value="true" />
<property name="exposeSpringMacroHelpers" value="true" />
<property name="requestContextAttribute" value="request" />
<!-- 给视图解析器配置优先級,你可以给之前jsp视图解析器的值配为2 -->
<property name="order" value="1" />
</bean>

</beans>

模板文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
<title>FreeMarker Hello World</title>
</head>
<body>
<table class="datatable">
<#list users as user>
<tr>
<td>${user.id}</td> <td>${user.name}</td>
</tr>
</#list>
</table>
</body>
</html>

控制器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller
public class DemoController {
@RequestMapping("/demo")
public String demo(Model model) {
List<Map<String, Object>> users = new ArrayList<>();
for (int i = 0; i < 3; i++) {
Map<String, Object> user = new HashMap<>();
user.put("id", i);
user.put("name", "Hello Spring FreeMarker");
users.add(user);
}
model.addAttribute("users", users);
return "demo";
}
}

springboot整合rabbitmq

发表于 2018-02-14 | 分类于 spring boot

引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?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 http://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>1.5.10.RELEASE</version>
</parent>
<groupId>com.hu</groupId>
<artifactId>spring-boot-rabbitmq</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
</project>

配置文件

1
2
3
4
5
6
7
8
9
10
spring:
rabbitmq:
host: 192.168.1.10
port: 5672
username: guest
password: guest
publisher-confirms: true # 消息发送到交换机确认机制,是否确认回调
server:
port: 8086
address: localhost

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.hu.controller;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class ProducerHello {
@Autowired
private AmqpTemplate rabbitTemplate;

@RequestMapping("hello")
@ResponseBody
public String sendHello() {
CorrelationData correlationId = new CorrelationData("000001");
//rabbitTemplate是使用springboot 提供的默认实现
System.out.println("------------1-----------");
this.rabbitTemplate.convertAndSend(correlationId.getId(), "Hello");
return "Send Success";
}
}

消费处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.hu.receiver;

import com.hu.config.RabbitMQConfig;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
* 消息消费者
*/
@Component
//监听队列
@RabbitListener(queues = RabbitMQConfig.QUEUE_NAME)
public class DemoReceiver {

@RabbitHandler //指定消息的处理方法
public void handleMessage(String message) throws Exception {
System.out.println("------------2-----------");
// 处理消息
System.out.println("handleMessage :" + message);
}

}

RabbitMQ最基础配置

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class RabbitMQConfig {

public static final String QUEUE_NAME="hello";
/**
* 创建一个队列,也可以去队列管理器中手动创建一个
* @return
*/
@Bean
public Queue Queue() {
return new Queue(QUEUE_NAME);
}
}

入口:

1
2
3
4
5
6
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}

运行后浏览

20200420150127

2、高级配置

单需要多个队列,并需要组合队列,交换机使用,这时需要配置。

properties-yaml配置文件和多环境配置以及打包发布运行

发表于 2018-02-14 | 分类于 spring boot

Spring Boot配置文件

Spring Boot 整个应用程序只有一个配置文件.properties 或 .yml 文件。Spring Boot 对每个配置项都有默认值,如果都是用默认值,连配置文件都可以不用。当然,我们也可以添加配置文件,用以覆盖其默认值。

properties 是以逗号隔开,而 yaml 则换行+ tab 隔开,这里需要注意的是冒号后面必须空格,否则会报错。yaml 文件格式更清晰,更易读、

20200420142834

多环境配置

在一个企业级系统中,开发时使用开发环境,测试时使用测试环境,上线时使用生产环境。每个环境的配置都可能不一样,比如开发环境的数据库是本地地址,而测试环境的数据库是测试地址。那我们在打包的时候如何生成不同环境的包。

首先,创建 application.yml 文件,在里面添加如下内容:

1
2
3
spring:
profiles:
active: dev

含义是指定当前项目的默认环境为 dev,即项目启动时如果不指定任何环境,Spring Boot 会自动从 dev 环境文件中读取配置信息。我们可以将不同环境都共同的配置信息写到这个文件中。

然后创建多环境配置文件,文件名的格式为:application-{profile}.yml,其中,{profile} 替换为环境名字,如 application-dev.yml,我们可以在其中添加当前环境的配置信息,如添加数据源:

1
2
3
4
5
6
spring:
datasource:
url: jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=UTF-8&useSSL=true
username: root
password: root
driverClassName: com.mysql.jdbc.Driver

这样,我们就实现了多环境的配置,每次编译打包我们无需修改任何东西,编译为 jar 文件后,运行命令:

1
java -jar api.jar --spring.profiles.active=dev

其中就是我们要指定的环境。

打包发布与运行

war包运行

pom:

1
2
3
4
5
6
7
8
9
<packaging>war</packaging>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<build>
<finalName>index</finalName>
</build>

mvn package 就会生成 war 包,然后放到 Tomcat 当中就能启动,但是这样配置在 Tomcat 是不能成功运行的会报错,需要通过编码指定 Tomcat 容器启动,修改 HelloController 类:

1
2
3
4
5
6
7
8
9
10
public class HelloController extends SpringBootServletInitializer{
@RequestMapping("hello")
String hello() {
return "Hello World!";
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}

由于我们采用web3.0 规范,是没有web.xml 的,而此类的作用与web.xml相同。

这时再打包放到 Tomcat,启动就不会报错了。

jar包运行

1
2
3
4
5
6
7
8
9
10
11
12
13
<packaging>jar</packaging>
<build>
<finalName>api</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.xxx.xxxxxxApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>

然后通过 mvn package 打包,最后通过 java 命令启动:java -jar api.jar

springboot热部署与发布

springboot有3中热部署方式:

1.使用springloaded配置pom.xml文件,使用mvn spring-boot:run启动
2.使用springloaded本地加载启动,配置jvm参数  -javaagent:<jar包地址> -noverify
3.使用devtools工具包,操作简单,但是每次需要重新部署

spring为开发者提供了一个名为spring-boot-devtools的模块来使Spring Boot应用支持热部署,提高开发者的开发效率,无需手动重启Spring Boot应用。

热部署方式在IDEA是默认没有打开自动编译的,手动编译需要快捷键(Ctrl+Shift+F9),自动编译的修改配置如下:File-Settings-Compiler-Build Project automatically

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>

方式一:

IDEA设置:

1
2
 “File” -> “Settings” -> “Build,Execution,Deplyment” -> “Compiler”,选中打勾 “Build project automatically” 。
组合键:“Shift+Ctrl+Alt+/” ,选择 “Registry” ,选中打勾 “compiler.automake.allow.when.app.running”

方式二:

配置:

1
2
3
4
5
6
#热部署生效
spring.devtools.restart.enabled: true
#设置重启的目录
#spring.devtools.restart.additional-paths: src/main/java
#classpath目录下的WEB-INF文件夹内容修改不重启
spring.devtools.restart.exclude: WEB-INF/**

springboot整合rabbitmq

发表于 2018-02-14 | 分类于 spring boot

在Spring Security3的使用中,有4种方法:

一种是全部利用配置文件,将用户、权限、资源(url)硬编码在xml文件中,已经实现过,并经过验证;

二种是用户和权限用数据库存储,而资源(url)和权限的对应采用硬编码配置,目前这种方式已经实现,并经过验证。

三种是细分角色和权限,并将用户、角色、权限和资源均采用数据库存储,并且自定义过滤器,代替原有的FilterSecurityInterceptor过滤器,
并分别实现AccessDecisionManager、InvocationSecurityMetadataSourceService和UserDetailsService,并在配置文件中进行相应配置。
目前这种方式已经实现,并经过验证。
http://blog.csdn.net/woshisap/article/details/7250428

添加spring security到我们应用中第一步是要创建Spring Security Java 配置类。
这个配置创建一个叫springSecurityFilterChain的Servlet过滤器,来对我们应用中所有的安全相关的事项(保护应用的所有url,验证用户名密码,表单重定向等)负责。

spring security的简单原理:
使用众多的拦截器对url拦截,以此来管理权限。但是这么多拦截器主要说明登陆验证拦截器AuthenticationProcessingFilter和访问的资源管理夫人资源管理拦截器AbstractSecurityInterceptor。

但拦截器里面的实现需要一些组件来实现,所以就有了AuthenticationManager、accessDecisionManager等组件来支撑。

现在先大概过一遍整个流程,
用户登陆,会被AuthenticationProcessingFilter拦截(即认证管理),调用AuthenticationManager的实现,而且AuthenticationManager会调用ProviderManager来获取用户验证信息(不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等),
如果验证通过后会将用户的权限信息封装一个User放到spring的全局缓存SecurityContextHolder中,以备后面访问资源时使用。

访问资源(即授权管理),访问url时,会通过AbstractSecurityInterceptor拦截器拦截,
其中会调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限,
在调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面。

示例:数据库动态管理用户、角色、权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
DROP TABLE IF EXISTS `Sys_User`;
CREATE TABLE `Sys_User`(
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`username` VARCHAR(200) NOT NULL,
`password` VARCHAR(200) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

DROP TABLE IF EXISTS `Sys_Role`;
CREATE TABLE `Sys_Role`(
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(200) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

DROP TABLE IF EXISTS `Sys_permission`;
CREATE TABLE `Sys_permission`(
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(200) NOT NULL,
`description` VARCHAR(200) DEFAULT NULL,
`url` VARCHAR(200) NOT NULL,
`pid` BIGINT DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


DROP TABLE IF EXISTS `Sys_role_user`;
CREATE TABLE `Sys_role_user`(
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`sys_user_id` BIGINT UNSIGNED NOT NULL,
`sys_role_id` BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


DROP TABLE IF EXISTS `Sys_permission_role`;
CREATE TABLE `Sys_permission_role`(
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`role_id` BIGINT UNSIGNED NOT NULL,
`permission_id` BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

insert into Sys_User (id,username, password) values (1,'admin', 'admin');
insert into Sys_User (id,username, password) values (2,'abel', 'abel');

insert into Sys_Role(id,name) values(1,'ROLE_ADMIN');
insert into Sys_Role(id,name) values(2,'ROLE_USER');

insert into Sys_role_user(SYS_USER_ID,sys_role_id) values(1,1);
insert into Sys_role_user(SYS_USER_ID,sys_role_id) values(2,2);

INSERT INTO `Sys_permission` VALUES ('1', 'ROLE_HOME', 'home', '/', null), ('2', 'ROLE_ADMIN', 'ABel', '/admin', null);
INSERT INTO `Sys_permission_role` VALUES ('1', '1', '1'), ('2', '1', '2'), ('3', '2', '1');

demo例子在git学习项目spring-boot-security-demo

启用应用,并访问http://localhost:8080/,可以正常访问。

但是访问http://localhost:8080/hello
被重定向到了http://localhost:8080/login页面,因为没有登录,用户没有访问权限,通过输入用户名user和密码password进行登录后,跳转到了Hello World页面,再也通过访问http://localhost:8080/login?logout,就可以完成注销操作。

spring-boot整合数据持久层

发表于 2018-02-14 | 分类于 spring boot

orm框架的本质是简化编程中操作数据库的编码,发展到现在基本上就剩两家了,一个是宣称可以不用写一句SQL的hibernate,一个是可以灵活调试动态sql的mybatis,两者各有特点,在企业级系统开发中可以根据需求灵活使用。发现一个有趣的现象:传统企业大都喜欢使用hibernate,互联网行业通常使用mybatis。

hibernate特点就是所有的sql都用Java代码来生成,不用跳出程序去写(看)sql,有着编程的完整性,发展到最顶端就是spring data jpa这种模式了,基本上根据方法名就可以生成对应的sql了

mybatis初期使用比较麻烦,需要各种配置文件、实体类、dao层映射关联、还有一大推其它配置。当然mybatis也发现了这种弊端,初期开发了generator可以根据表结果自动生产实体类、配置文件和dao层代码,可以减轻一部分开发量;后期也进行了大量的优化可以使用注解了,自动管理dao层和配置文件等,发展到最顶端就是今天要讲的这种模式了,mybatis-spring-boot-starter就是springboot+mybatis可以完全注解不用配置文件,也可以简单配置轻松上手。

一、Mybatis

mybatis初期使用比较麻烦,需要各种配置文件、实体类、dao层映射关联、还有一大推其它配置。
当然mybatis也发现了这种弊端,初期开发了generator可以根据表结果自动生产实体类、配置文件和dao层代码,可以减轻一部分开发量;后期也进行了大量的优化可以使用注解了,自动管理dao层和配置文件等。
发展到最顶端就是今天要讲的这种模式了,mybatis-spring-boot-starter就是springboot+mybatis可以完全注解不用配置文件,也可以简单配置轻松上手。

mybatis-spring-boot-starter

官方说明:MyBatis Spring-Boot-Starter will help you use MyBatis with Spring Boot

其实就是myBatis看springboot这么火热也开发出一套解决方案来,使用起来确实顺畅了许多。mybatis-spring-boot-starter主要有两种解决方案,一种是使用注解解决一切问题,一种是简化后的老传统。

1、无配置文件,全部使用注解方式

创建项目,引入包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
 <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-data</artifactId>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>

创建配置文件,写入配置信息:

1
2
3
4
5
mybatis.type-aliases-package=com.hu.pojo
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://192.168.1.10:33061/demo?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false
spring.datasource.username = root
spring.datasource.password = 123456

Mapper接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.hu.mapper;

import com.hu.pojo.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface UserMapper {
/**
* 查询所有用户
*
* @return
*/
@Select("SELECT * FROM users")
@Results({
@Result(property = "id", column = "id" ),
@Result(property = "name", column = "name"),
@Result(property = "userName", column = "useName"),
@Result(property = "passWord", column = "passWord"),
@Result(property = "age", column = "age")
})
List<User> getAll();

/**
* 查询单个用户
*
* @param id 用户ID
* @return
*/
@Select("SELECT * FROM users WHERE id = #{id}")
User getUserById(int id);

/**
* 插入用户
*
* @param user 用户对象
*/
@Insert("INSERT INTO users VALUES(default,#{name}, #{passWord}, #{userName}, #{age})")
void insert(User user);

/**
* 更新用户信息
*
* @param user
*/
@Update("UPDATE users SET name=#{name},userName=#{userName},passWord=#{passWord},age=#{age} WHERE id =#{id}")
void update(User user);


/**
* 删除指定用户
*
* @param id
*/
@Delete("DELETE FROM users WHERE id =#{id}")
void delete(int id);
}

实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.hu.pojo;

public class User {
private int id;
private String name;
private String passWord;
private String userName;
private int age;

public int getId() {
return id;
}

public String getName() {
return name;
}

public String getPassWord() {
return passWord;
}

public String getUserName() {
return userName;
}

public int getAge() {
return age;
}

public void setId(int id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

public void setPassWord(String passWord) {
this.passWord = passWord;
}

public void setUserName(String userName) {
this.userName = userName;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", passWord='" + passWord + '\'' +
", userName='" + userName + '\'' +
", age=" + age +
'}';
}

public User(String name, String passWord, String userName, int age) {
this.name = name;
this.passWord = passWord;
this.userName = userName;
this.age = age;
}
public User(int id, String name, String passWord, String userName, int age) {
this.id = id;
this.name = name;
this.passWord = passWord;
this.userName = userName;
this.age = age;
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 import com.hu.AppData;
import com.hu.mapper.UserMapper;
import com.hu.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Arrays;
import java.util.List;


@RunWith(SpringRunner.class)
@SpringBootTest(classes=AppData.class)
public class UserMapperTest {
@Autowired
private UserMapper userMapper;

@Test
public void test() throws Exception {
System.out.println("------------------------插入数据之前:-----------------------------");
List<User> users = userMapper.getAll();
System.out.println(Arrays.toString(users.toArray()));
System.out.println("------------------------插入数据之后:-----------------------------");
userMapper.insert(new User("黄晓明","1111","huang",111));
users = userMapper.getAll();
System.out.println(Arrays.toString(users.toArray()));
System.out.println("------------------------获取ID为1-----------------------------");
User user = userMapper.getUserById(1);
System.out.println(user);
System.out.println("------------------------更新ID为1-----------------------------");
user.setName("张真人");
userMapper.update(user);
System.out.println("------------------------更新ID为1后-----------------------------");
System.out.println(userMapper.getUserById(1));
System.out.println("------------------------删除ID为2后-----------------------------");
userMapper.delete(2);
users = userMapper.getAll();
System.out.println(Arrays.toString(users.toArray()));
}
}

结果:

1
2
3
4
5
6
7
8
9
10
 [User{id=1, name='张三丰', passWord='111111', userName='zhangsanfeng', age=80}, User{id=2, name='胡一刀', passWord='111111', userName='111111', age=22}]
------------------------插入数据之后:-----------------------------
[User{id=1, name='张三丰', passWord='111111', userName='zhangsanfeng', age=80}, User{id=2, name='胡一刀', passWord='111111', userName='111111', age=22}, User{id=8, name='黄晓明', passWord='huang', userName='huang', age=111}]
------------------------获取ID为1-----------------------------
User{id=1, name='张三丰', passWord='111111', userName='zhangsanfeng', age=80}
------------------------更新ID为1-----------------------------
------------------------更新ID为1后-----------------------------
User{id=1, name='张真人', passWord='111111', userName='zhangsanfeng', age=80}
------------------------删除ID为2后-----------------------------
[User{id=1, name='张真人', passWord='111111', userName='zhangsanfeng', age=80}, User{id=8, name='黄晓明', passWord='111111', userName='huang', age=111}]

2、可以增加一点xml配置信息

比如在spring配置中增加mybatis配置信息,把sql代码写在xml中
mybatis.config-locations=classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

二、Redis

Redis是目前业界使用最广泛的内存数据存储。相比memcached,Redis支持更丰富的数据结构,例如hashes, lists, sets等,同时支持数据持久化。除此之外,Redis还提供一些类数据库的特性,比如事务,HA,主从库。可以说Redis兼具了缓存系统和数据库的一些特性,因此有着丰富的应用场景。

1、缓存的作用

创建项目,上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 <?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 http://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>1.5.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-boot-data-redis</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.3</version>
</dependency>
</dependencies>
</project>

配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=192.168.1.10
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=0

Redis配置类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.hu;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;

//自定义缓存key生成策略
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator(){
@Override
public Object generate(Object target, java.lang.reflect.Method method, Object... params) {
StringBuffer sb = new StringBuffer();
sb.append(target.getClass().getName());
sb.append(method.getName());
for(Object obj:params){
sb.append(obj.toString());
}
return sb.toString();
}
};
}
//缓存管理器
@Bean
public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
//设置缓存过期时间
cacheManager.setDefaultExpiration(10000);
return cacheManager;
}
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory){
StringRedisTemplate template = new StringRedisTemplate(factory);
//设置序列化工具
setSerializer(template);
return template;
}
@SuppressWarnings("all")
private void setSerializer(StringRedisTemplate template){
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//查看Redis的Bean定义发现,对key的序列化使用的是StringRedisSerializer系列化,value值的序列化是GenericJackson2JsonRedisSerializer的序列化方法。
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
}
}

实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
 package com.hu;

public class User implements java.io.Serializable{
private int id;
private String name;
private String passWord;
private String userName;
private int age;

public int getId() {
return id;
}

public String getName() {
return name;
}

public String getPassWord() {
return passWord;
}

public String getUserName() {
return userName;
}

public int getAge() {
return age;
}

public void setId(int id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

public void setPassWord(String passWord) {
this.passWord = passWord;
}

public void setUserName(String userName) {
this.userName = userName;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", passWord='" + passWord + '\'' +
", userName='" + userName + '\'' +
", age=" + age +
'}';
}

public User(String name, String passWord, String userName, int age) {
this.name = name;
this.passWord = passWord;
this.userName = userName;
this.age = age;
}
public User(int id, String name, String passWord, String userName, int age) {
this.id = id;
this.name = name;
this.passWord = passWord;
this.userName = userName;
this.age = age;
}
public User(){}
}

入口:

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableCaching//开启缓存
public class Application {
public static void main(String[] args) {

SpringApplication.run(Application.class, args);
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.hu;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.concurrent.TimeUnit;

//@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
@SpringBootTest(classes=Application.class)
public class TestRedis {

/**
* StringRedisTemplate与RedisTemplate
*
* 两者的关系是StringRedisTemplate继承RedisTemplate。
* 也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,
* RedisTemplate只能管理RedisTemplate中的数据。
*
* SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
* StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
* RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
*
*/
@Autowired
private StringRedisTemplate stringRedisTemplate;

@Autowired
private RedisTemplate redisTemplate;

//手动使用的方式缓存
@Test
public void test() throws Exception {
stringRedisTemplate.opsForValue().set("hu", "123");
System.out.println(stringRedisTemplate.opsForValue().get("hu"));

User user=new User("何晓明","123456","hexiaoming",22);
ValueOperations<String, User> operations=redisTemplate.opsForValue();
operations.set("user1", user);
operations.set("user2",user,1, TimeUnit.SECONDS);
Thread.sleep(1000);
System.out.println(redisTemplate.hasKey("user2"));
operations.set("user3", user);
//redisTemplate.delete("user3");
//System.out.println(operations.get("user1"));
}

@Autowired
UserController userController;
@Test
public void userQues(){
System.out.println(userController.user());
}
}
@Service
class UserController{
//自动根据方法生成缓存
@Cacheable(value="user-key")//value的值就是缓存到redis中的key
public User user() {
User user= selUser();
return user;
}
public User selUser(){
System.out.println("redis缓存中没有,调用");//模拟查询数据库操作
return new User("何晓明","123456","hexiaoming",22);
}
}

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
test方法:
123
false

userQues方法:

当第一次运行,没缓存,掉查询,这里是模拟,运行结果

redis缓存中没有,调用

User{id=0, name='何晓明', passWord='123456', userName='hexiaoming', age=22}

第二次直接去缓存:
User{id=0, name='何晓明', passWord='123456', userName='hexiaoming', age=22}

刚开始由于没写实体类无参构造方法,导致抛这个异常:
org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Can not construct instance of com.hu.User: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)

2、实现共享Session

引入依赖

1
2
3
4
5
6
7
8
 <dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

添加SessionConfig

1
2
3
4
5
6
7
8
/**
* maxInactiveIntervalInSeconds: 设置Session失效时间,
* 使用Redis Session之后,原Boot的server.session.timeout属性不再生效
*/
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)
public class SessionConfig {
}

Controller中添加一个方法:

1
2
3
4
5
6
7
8
9
 @RequestMapping("/uid")
public String uid(HttpSession session) {
UUID uid = (UUID) session.getAttribute("uid");
if (uid == null) {
uid = UUID.randomUUID();
}
session.setAttribute("uid", uid);
return session.getId();
}

运行,浏览器输入http://localhost:8080/uid

查看Redis中也有数据

访问session返回还是一样的session

这是一种使用session最基本方式。

3、总结使用redis过程

添加依赖

1
2
3
4
 <dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>

application.properties中加入redis相关配置后,增加redis配置类。

其实可以不用配置,直接使用RedisTemplate,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration
protected static class RedisConfiguration {
protected RedisConfiguration() {
}

@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

@Bean
@ConditionalOnMissingBean({StringRedisTemplate.class})
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}

通过源码得到,SpringBoot自动在容器中生成了一个RedisTemplate和一个StringRedisTemplate。但是,这个RedisTemplate的泛型是<Object,Object>,并且没有序列化。RedisTemplate的泛型是<Object,Object>,为了方便还创建了一个泛型为<String,Object>形式的RedisTemplate。

@ConditionalOnMissingBean注解,说明如果Spring容器中有了RedisTemplate对象了,这个自动配置的RedisTemplate不会实例化。因此我们可以直接自己写个配置类,配置RedisTemplate。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.hu.redis;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
//序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;

}
}

接下来就可以直接用RedisTemplate操作Redis,封装成为一个工具类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
package com.hu.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public class RedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;

/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}

/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 删除key
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}

/**
* 获取key
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}

/**
* 存入key
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* key存放并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}

/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}

/**
* HashHet
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}

/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}

/**
* HashSet
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 删除hash表中的值
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}

/**
* 判断hash表中是否有该项的值
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}

/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}

/**
* hash递减
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}

/**
* 根据key获取Set中的所有值
*
* @param key 键
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 根据value从一个set中查询,是否存在
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 将数据放入set缓存
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 将set数据放入缓存
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 获取set缓存的长度
* @param key 键
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 移除值为value的
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 获取list缓存的长度
* @param key 键
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}

/**
* 通过索引 获取list中的值
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 移除N个值为value
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}

之后就可以根据实际需要来操作Redis

三、JPA

JPA(Java Persistence API)是Sun官方提出的Java持久化规范。它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据。他的出现主要是为了简化现有的持久化开发工作和整合ORM技术,结束现在Hibernate,TopLink,JDO等ORM框架各自为营的局面。值得注意的是,JPA是在充分吸收了现有Hibernate,TopLink,JDO等ORM框架的基础上发展而来的,具有易于使用,伸缩性强等优点。

JPA是一套规范,不是一套产品,如果某个产品这个JPA规范,叫JPA的实现。

Data JPA 是 spring data 项目下的一个模块,是Spring 基于 ORM 框架。提供了一套基于 JPA标准操作数据库的简化方案。底层默认的是依赖 Hibernate JPA 来实现的。

spring data jpa让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现。

基本查询也分为两种,一种是spring data默认已经实现,一种是根据查询的方法来自动解析成SQL。

如何使用JPA

查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
查询所有数据 findAll()
分页查询 findAll(new PageRequest(0, 2))
根据id查询 findOne()
根据实体类属性查询: findByProperty (type Property); 例如:findByAge(int age);
排序: findAll(sort )
Sort sort = new Sort(Sort.Direction.DESC, "age").and (new Sort(Sort.Direction.DESC, "id"));
条件查询 and/or/findByAgeLessThan/LessThanEqual 等,
例如: findByUsernameAndPassword(String username , String password)
总数 查询 count() 或者 根据某个属性的值查询总数countByAge(int age);
是否存在某个id exists()
修改,删除,新增

新增:直接使用 save(T) 方法
删除: delete() 或者 deleteByProperty 例如:deleteByAge(int age) ;
更新:
@Modifying
@Query("update Customer u set u.age = ?1 where u.id = ?2")
int update(int age1 , long id);

引入配置:

注意jpa和mysql包顺序,之前由于顺序反过来,启动时候报异常:IllegalArgumentException: At least one JPA metamodel must be present

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-boot-data-jpa</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<!--<exclusions>
<exclusion>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</exclusion>
</exclusions>-->
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>

配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 ########################################################
###数据库连接信息
########################################################
#连接地址
spring.datasource.url=jdbc:mysql://192.168.1.10:33061/demo
#数据库账户
spring.datasource.username=root
#数据库密码
spring.datasource.password=123456
#数据库驱动
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
########################################################
### Java Persistence Api JPA相关配置
########################################################
#指定数据库类型
spring.jpa.database=mysql
#控制台打印sql
spring.jpa.show-sql=true
#建表策略,这里用update,即根据实体更新表结构
#create:每次运行程序时,都会重新创建表,故而数据会丢失
#create-drop:每次运行程序时会先创建表结构,然后待程序结束时清空表
#upadte:每次运行程序,没有表时会创建表,如果对象发生改变会更新表结构,原有数据不会清空,只会更新(推荐使用)
#validate:运行程序会校验数据与数据库的字段类型是否相同,字段不同会报错
#none: 禁用DDL处理
spring.jpa.hibernate.ddl-auto=update
#表中字段命名策略,这里要引入hibernate的核心包,不然这个命名策略会报错
#spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
#spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
#spring.jpa.hibernate.naming.strategy=org.hibernate.cfg.ImprovedNamingStrategy
#方言
#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

Repository:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.hu.dao;

import com.hu.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
* spring data jpa让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现,需要实现JpaRepository接口
*
* Repository: 最顶层的接口,是一个空接口,目的是为了统一所有的Repository的类型,且能让组件扫描时自动识别
* CrudRepository: Repository的子接口,提供CRUD 的功能。
* PagingAndSortingRepository:CrudRepository的子接口, 添加分页排序。
* JpaRepository: PagingAndSortingRepository的子接口,增加批量操作等。
* JpaSpecificationExecutor: 用来做复杂查询的接口。
*
* 一般使用JpaRepository这个接口做查询即可.这个接口拥有如下方法:
* delete删除或批量删除
* findOne查找单个
* findAll查找所有
* save保存单个或批量保存
* saveAndFlush保存并刷新到数据库
*
*/
@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {

}

也可以直接在这个接口中写对应需要的方法,如下;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
/**
* Spring-data-jpa的新特性,通过解析方法名创建查询。更多解析说明如下:
* And => 等价于 SQL 中的 and 关键字 例如:findByUsernameAndPassword(String user, Striang pwd);
* Or => 等价于 SQL 中的 or 关键字,例如:findByUsernameOrAddress(String user, String addr);
* Between => 等价于 SQL 中的 between 关键字,例如:SalaryBetween(int max, int min);
* LessThan => 等价于 SQL 中的 "<",例如: findBySalaryLessThan(int max);
* GreaterThan => 等价于 SQL 中的">",例如: findBySalaryGreaterThan(int min);
* 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);
* 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 类型,也可以是数组或者不定长参数;
* 创建一个按单字段排序的Sort对象: new Sort(Sort.Direction.DESC, "description").and(new Sort(Sort.Direction.ASC, "id"))
*/
Student findByName(String name);
Student findById(Long id);
List<Student> findByNameLike(String name);
//使用JPQL
@Query("from User u where u.name=:name")
Student findStudent(@Param("name") String name);

}

入口:

1
2
3
4
5
6
 @SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 @RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class Test {
@Autowired
private StudentRepository studentRepository;

@org.junit.Test
public void test() {
Long id = new Long((long)1);
for (int i=3;i>=0;i--){
studentRepository.save(new Student("Jack "+i));
}
System.out.println(Arrays.toString(studentRepository.findAll().toArray()));

}
}

结果:

1
2
3
4
5
6
7
8
 2019-04-15 09:36:37.101  INFO 34776 --- [           main] com.hu.test.Test                         : Started Test in 4.964 seconds (JVM running for 5.792)
Hibernate: insert into student (name) values (?)
Hibernate: insert into student (name) values (?)
Hibernate: insert into student (name) values (?)
Hibernate: insert into student (name) values (?)
2019-04-15 09:36:37.390 INFO 34776 --- [ main] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select student0_.id as id1_0_, student0_.name as name2_0_ from student student0_
[Student{id=14, name='Jack 3'}, Student{id=15, name='Jack 2'}, Student{id=16, name='Jack 1'}, Student{id=17, name='Jack 0'}]

测试完全没问题,都不用建表,jpa帮我们自动建立表格。

问题:

1
2
3
4
5
6
检查一下包是否引入正确,引入一下:
import javax.persistence.*;
中文乱码问题:
spring.datasource.url=jdbc:mysql://192.168.1.10:33061/demo?characterEncoding=utf-8
在高版本mysql中需要指定是否进行SSL连接
spring.datasource.url=jdbc:mysql://192.168.1.10:33061/demo?characterEncoding=utf-8&useSSL=false

springboot常用注解解释说明

发表于 2018-02-14 | 分类于 spring boot

一、注解(annotations)列表

  • @SpringBootApplication:包含了@ComponentScan、@Configuration和@EnableAutoConfiguration注解。其中@ComponentScan让spring Boot扫描到Configuration类并把它加入到程序上下文。
  • @Configuration 等同于spring的XML配置文件;使用Java代码可以检查类型安全。
  • @EnableAutoConfiguration 自动配置。
  • @ComponentScan 组件扫描,可自动发现和装配一些Bean。
  • @Component可配合CommandLineRunner使用,在程序启动后执行一些基础任务。
  • @RestController注解是@Controller和@ResponseBody的合集,表示这是个控制器bean,并且是将函数的返回值直 接填入HTTP响应体中,是REST风格的控制器。
  • @Autowired自动导入。
  • @PathVariable获取参数。
  • @JsonBackReference解决嵌套外链问题。
  • @RepositoryRestResourcepublic配合spring-boot-starter-data-rest使用。

二、注解(annotations)详解

@SpringBootApplication:申明让spring boot自动给程序进行必要的配置,这个配置等同于:@Configuration ,@EnableAutoConfiguration 和 @ComponentScan 三个配置。

1
2
3
4
5
6
@SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

@ResponseBody:表示该方法的返回结果直接写入HTTP response body中,一般在异步获取数据时使用,用于构建RESTful的api。在使用@RequestMapping后,返回值通常解析为跳转路径,加上@responsebody后返回结果不会被解析为跳转路径,而是直接写入HTTP response body中。比如异步获取json数据,加上@responsebody后,会直接返回json数据。该注解一般会配合@RequestMapping一起使用。示例代码:

1
2
3
4
5
@RequestMapping(“/test”)
@ResponseBody
public String test(){
return”ok”;
}

@Controller:用于定义控制器类,在spring 项目中由控制器负责将用户发来的URL请求转发到对应的服务接口(service层),一般这个注解在类中,通常方法需要配合注解@RequestMapping。示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
@RequestMapping(“/demoInfo”)
publicclass DemoController {
@Autowired
private DemoInfoService demoInfoService;
@RequestMapping("/hello")
public String hello(Map<String,Object> map){
System.out.println("DemoController.hello()");
map.put("hello","from TemplateController.helloHtml");
//会使用hello.html或者hello.ftl模板进行渲染显示.
return"/hello";
}
}

@RestController:用于标注控制层组件(如struts中的action),@ResponseBody和@Controller的合集。示例代码:

1
2
3
4
5
6
7
8
@RestController
@RequestMapping(“/demoInfo2”)
publicclass DemoController2 {
@RequestMapping("/test")
public String test(){
return"ok";
}
}

@RequestMapping:提供路由信息,负责URL到Controller中的具体函数的映射。

@EnableAutoConfiguration:Spring Boot自动配置(auto-configuration):尝试根据你添加的jar依赖自动配置你的Spring应用。例如,如果你的classpath下存在HSQLDB,并且你没有手动配置任何数据库连接beans,那么我们将自动配置一个内存型(in-memory)数据库”。你可以将@EnableAutoConfiguration或者@SpringBootApplication注解添加到一个@Configuration类上来选择自动配置。如果发现应用了你不想要的特定自动配置类,你可以使用@EnableAutoConfiguration注解的排除属性来禁用它们。

@ComponentScan:表示将该类自动发现扫描组件。个人理解相当于,如果扫描到有@Component、@Controller、@Service等这些注解的类,并注册为Bean,可以自动收集所有的Spring组件,包括@Configuration类。我们经常使用@ComponentScan注解搜索beans,并结合@Autowired注解导入。可以自动收集所有的Spring组件,包括@Configuration类。我们经常使用@ComponentScan注解搜索beans,并结合@Autowired注解导入。如果没有配置的话,Spring Boot会扫描启动类所在包下以及子包下的使用了@Service,@Repository等注解的类。

@Configuration:相当于传统的xml配置文件,如果有些第三方库需要用到xml文件,建议仍然通过@Configuration类作为项目的配置主类——可以使用@ImportResource注解加载xml配置文件。

@Import:用来导入其他配置类。

@ImportResource:用来加载xml配置文件。

@Autowired:自动导入依赖的bean

@Service:一般用于修饰service层的组件

@Repository:使用@Repository注解可以确保DAO或者repositories提供异常转译,这个注解修饰的DAO或者repositories类会被ComponetScan发现并配置,同时也不需要为它们提供XML配置项。

@Bean:用@Bean标注方法等价于XML中配置的bean。

@Value:注入Spring boot application.properties配置的属性的值。示例代码:

@Value(value = “#{message}”)
private String message;
@Inject:等价于默认的@Autowired,只是没有required属性;

@Component:泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

@Bean:相当于XML中的,放在方法的上面,而不是类,意思是产生一个bean,并交给spring管理。

@AutoWired:自动导入依赖的bean。byType方式。把配置好的Bean拿来用,完成属性、方法的组装,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。当加上(required=false)时,就算找不到bean也不报错。

@Qualifier:当有多个同一类型的Bean时,可以用@Qualifier(“name”)来指定。与@Autowired配合使用。@Qualifier限定描述符除了能根据名字进行注入,但能进行更细粒度的控制如何选择候选者,具体使用方式如下:

1
2
3
@Autowired
@Qualifier(value = “demoInfoService”)
private DemoInfoService demoInfoService;

@Resource(name=”name”,type=”type”):没有括号内内容的话,默认byName。与@Autowired类似。

三、JPA注解

@Entity:@Table(name=”“):表明这是一个实体类。一般用于jpa这两个注解一般一块使用,但是如果表名和实体类名相同的话,@Table可以省略

@MappedSuperClass:用在确定是父类的entity上。父类的属性子类可以继承。

@NoRepositoryBean:一般用作父类的repository,有这个注解,spring不会去实例化该repository。

@Column:如果字段名与列名相同,则可以省略。

@Id:表示该属性为主键。

@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = “repair_seq”):表示主键生成策略是sequence(可以为Auto、IDENTITY、native等,Auto表示可在多个数据库间切换),指定sequence的名字是repair_seq。

@SequenceGeneretor(name = “repair_seq”, sequenceName = “seq_repair”, allocationSize = 1):name为sequence的名称,以便使用,sequenceName为数据库的sequence名称,两个名称可以一致。

@Transient:表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性。如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则,ORM框架默认其注解为@Basic。@Basic(fetch=FetchType.LAZY):标记可以指定实体属性的加载方式

@JsonIgnore:作用是json序列化时将Java bean中的一些属性忽略掉,序列化和反序列化都受影响。

@JoinColumn(name=”loginId”):一对一:本表中指向另一个表的外键。一对多:另一个表指向本表的外键。

@OneToOne、@OneToMany、@ManyToOne:对应hibernate配置文件中的一对一,一对多,多对一。

四、springMVC相关注解

@RequestMapping:@RequestMapping(“/path”)表示该控制器处理所有“/path”的UR L请求。RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。

用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。该注解有六个属性:

params:指定request中必须包含某些参数值是,才让该方法处理。 
headers:指定request中必须包含某些指定的header值,才能让该方法处理请求。 
value:指定请求的实际地址,指定的地址可以是URI Template 模式 
method:指定请求的method类型, GET、POST、PUT、DELETE等 
consumes:指定处理请求的提交内容类型(Content-Type),如application/json,text/html; 
produces:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回

@RequestParam:用在方法的参数前面。

@RequestParam 
String a =request.getParameter(“a”)。

@PathVariable:路径变量。如

RequestMapping(“user/get/mac/{macAddress}”)
    public String getByMacAddress(@PathVariable String macAddress){
    //do something;
} 
参数与大括号里的名字一样要相同。

五、全局异常处理

@ControllerAdvice:包含@Component。可以被扫描到。统一处理异常。

@ExceptionHandler(Exception.class):用在方法上面表示遇到这个异常就执行以下方法。

FreeMarker-在servlet上使用

发表于 2018-02-13 | 分类于 前端

采用servlet+maven搭建demo

20200413165155

创建Maven项目,引入依赖包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hu</groupId>
<artifactId>maven-freemarker-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.20</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<!-- 控制 tomcat 端口号 -->
<port>801</port>
<!-- 项目发布到 tomcat 后的名称 -->
<path>/</path>
</configuration>
</plugin>
</plugins>
<!-- 配置编译文件 -->
<resources>
<!-- src/main/java 里面的资源文件进行编译到classes 中 -->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<!-- 上面修改了默认配置,打破了规则,也要配置把resources中的资源文件让其被编译到class -->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
</project>

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>freemarker</servlet-name>
<servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class>
<!--下面的配置freemarke的ftl文件或者html,jsp等页面的位置 -->
<init-param>
<param-name>TemplatePath</param-name>
<param-value>/freeMarker</param-value>
</init-param>
<!-- 可选配置 -->
<init-param>
<param-name>NoCache</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>ContentType</param-name>
<param-value>text/html; charset=UTF-8</param-value>
</init-param>
<init-param>
<param-name>template_update_delay</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>default_encoding</param-name>
<param-value>ISO-8859-1</param-value>
</init-param>
<init-param>
<param-name>number_format</param-name>
<param-value>0.##########</param-value>
</init-param>

<!-- 是否和服务器(tommcat)一起启动。-->
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>freemarker</servlet-name>
<url-pattern>*.ftl</url-pattern><!--页面的后缀,如果页面是ftl文件就写*.ftl,如果是html就写*.html -->
</servlet-mapping>
</web-app>

在创建模板文件:webapp/freeMarker/demo.ftl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
<title>FreeMarker Hello World</title>
</head>
<body>
<table class="datatable">
<#list users as user>
<tr>
<td>${user.id}</td> <td>${user.name}</td>
</tr>
</#list>
</table>
</body>
</html>

Servlet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@WebServlet("/free")
public class FreeMarkerServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println(1111111111);
List<Map<String, Object>> users = new ArrayList<>();
for (int i = 0; i < 3; i++) {
Map<String, Object> user = new HashMap<>();
user.put("id", i);
user.put("name", "Hello FreeMarker");
users.add(user);
}
request.setAttribute("users", users);
request.getRequestDispatcher("demo.ftl").forward(request, response);
}

}

FreeMarker-介绍

发表于 2018-02-13 | 分类于 前端

Freemarker 是一款模板引擎,是一种基于模版生成静态文件的通用 工具,它是为Java程序员提供的一个开发包,或者说是一个类库,它不是面向最终用户的,而是为程序员提供了一款可以嵌入他们开发产品的应用程序。

Freemarker 是使用纯java编写的,为了提高页面的访问速度,需要把页面静态化, 那么Freemarker就是被用来生成html页面。

到目前为止,Freemarker使用越来越广泛,不光光只是它强大的生成技术,而且它能够与spring进行很好的集成。

Freemarker生成静态页面的原理

Freemarker 生成静态页面,首先需要使用自己定义的模板页面,这个模板页面可以是最最普通的html,也可以是嵌套freemarker中的 取值表达式, 标签或者自定义标签等等,然后后台读取这个模板页面,解析其中的标签完成相对应的操作, 然后采用键值对的方式传递参数替换模板中的的取值表达式,做完之后 根据配置的路径生成一个新的html页面, 以达到静态化访问的目的。

Freemarker提供的标签

Freemarker提供了很多有用 常用的标签,Freemarker标签都是<#标签名称>这样子命名的,${value} 表示输出变量名的内容 ,具体如下:

1、list:该标签主要是进行迭代服务器端传递过来的List集合,比如:

<#list nameList as names>
${names}
</#list>

2、if: 该标签主要是做if判断用的,比如:

1
2
3
<#if (names=="")>  

</#if>

3、include:该标签用于导入文件用的。

1
<#include "include.html"/>

这个导入标签非常好用,特别是页面的重用。

另外在静态文件中可以使用${} 获取值,取值方式和el表达式一样,非常方便。

1
2
3
4
5
6
7
<!DOCTYPE  
<html>
<head>
<meta
<title>Insert
</head>
<body>

描述:${description}

java动态代理源码分析

发表于 2018-01-22 | 分类于 java

所谓动态代理,即通过代理类Proxy的代理,接口和实现类之间可以不直接发生联系,而可以在运行期(Runtime)实现动态关联。

Java动态代理类位于Java.lang.reflect包下,主要涉及到两个类。

(1)接口InvocationHandler:该接口中仅定义了一个方法。

1
2
Object invoke(Object obj, Method method, Object[] args);
在实际使用时,第一个参数obj一般指代理类,method是被代理的方法,args为该方法的参数数组。

(2)proxy:该类即为动态代理类,作用类实现了InvocationHandler接口的代理类,其中主要方法有:

1
2
3
protected Proxy(InvocationHandler h):构造方法,用于给内部的h赋值。
static Class getProxyClass(ClassLoader loader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用。
动态代理应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public interface Hello {
public void sayHello();
}
//实现接口
public class HelloEarth implements Hello {
@Override
public void sayHello() {
System.out.println("Hello earth!");
}

}

//实现接口增强
public class HelloEarthWrapper1 implements Hello {
private Hello helloImpl;
public HelloEarthWrapper1(Hello helloImpl) {
this.helloImpl = helloImpl;
}
@Override
public void sayHello() {
System.out.println("在sayHello之前执行了这条语句......");
helloImpl.sayHello();
}
}

//通过继承对类进行增强
public class HelloEarthWrapper2 extends HelloEarth {
private Hello helloImpl;
public HelloEarthWrapper2(Hello helloImpl) {
this.helloImpl = helloImpl;
}
@Override
public void sayHello() {
System.out.println("在sayHello之前执行了这条语句......");
helloImpl.sayHello();
}
}

//实现InvocationHandler接口
public class HelloHandler implements InvocationHandler {

private Object proxyed; // 被代理的类

// 被代理的对象
public HelloHandler(Object obj) {
this.proxyed = obj;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

Object result;
System.out.println("在sayHello()之前可以执行的语句");
// 调用原始对象的方法
result = method.invoke(this.proxyed, args);
System.out.println("在调用sayHello()之后");
return result;
}
}

//测试
@Test
public void test1() {
Hello helloImpl = new HelloEarth();
Hello helloWrapper = new HelloEarthWrapper2(helloImpl);
helloWrapper.sayHello();
}

@Test
public void test2() {
// 被代理类的实例
Hello hello = new HelloEarth();
// InvocationHandler接口,传入被代理的类

InvocationHandler handler = new HelloHandler(hello);

// 生成代理类
// 1. 给我被代理类的类加载器加载被代理的类,为啥要类加载器呢
// 2. 给我被代理类的接口信息
// 3. 给我如何增强接口中的方法信息
Hello proxy = (Hello) Proxy.newProxyInstance(hello.getClass()
.getClassLoader(), hello.getClass().getInterfaces(), handler);
// 调用代理的方法
proxy.sayHello();
}
使用包装类进行包装

包装类中有一个被包装的类的引用作为属性,和被包装类实现相同的接口,然后在实现接口的方法中调用和被包装类同名的接口中的方法实现方法的增强。这种解决方案的缺点就是当接口中的方法过多的时候就需要为接口中的每个方法都写一遍,即时不需要增强的方法也需要实现。

另一种做法是继承被包装的类,然后在包装类中重写需要增强的方法,但是缺点是只有在被包装类出现的地方才能使用被包装类,而实现接口的方法是在接口出现的地方都可以使用包装类。继承了被包装类之后的类,就是去了接口的好处,在包装类作为引用类型的地方必须的是被包装类及其子类,而不是接口出现。

使用动态代理

使用动态代理则可以不需要实现接口中所有的方法,又可以获得接口的好处。动态代理要求要包装的对象必须实现一个接口,该接口定义了准备在包装器中使用的所有方法,这一限制是鼓励面向接口的编程,而不是限制编程,根据经验,每个类至少应该实现一个接口。良好的接口用法不仅使动态代理成为可能,还有利于程序的模块化。

主要的步骤有:

首先根据被代理对象创建一个代理类

创建动态代理对象的proxy1,第一个参数为被代理类的类加载器,第二个参数为该类的接口,第三个对象为代理对象的实例。

通过调用代理对象中增强的方法

上述过程就是告诉Proxy类用一个指定的类加载器来动态创建一个对象,该对象要实现指定的接口,并用提供的InvocationHandler来代替传统的方法主体。在实现InvocationHandler接口的类中invoke()方法中,完全不存在对Hello接口的引用,在上述的例子中,以构造方法的传参的形式,为InvocationHandler的实现类提供了被代理接口的实例。代理接口实例上的任何方法调用最终都由InvocationHandler的实例委托给代理接口实例,这是最常见的设计。但是,InvocationHandler实例不一定非要委托给被代理的接口的另一个实例。事实上,InvocationHandler可以自行提供方法主体,完全不必委托给被代理类来实现。如果被代理接口发生变化,InvocationHandler中的实例仍然是可用的。

动态代理可以实现许多AOP方面的功能:安全、事务、日志、过滤、编码、解码等功能,而且纯粹是热插拔的。AOP的好处是可以对类的实例统一实现拦截操作,通常应用在日志、权限、缓存、连接池中等。

** 基于动态代理的字节码库 **

了解动态代理的原理之后,我们完全可以自己实现这样一个动态代理,只要生成该类的class文件的内存映像即可。有很多这样的工具。

BCEL:

SERP:

ASM:Java字节码汇编语言。ASM是轻量级的Java字节码处理框架,可以动态生成二进制格式的stub类或其他代理类,或者在类被Java虚拟机装载之前动态修改类。ASM设计的目的就是在运行时使用,因此要尽量体积小速度快。

cglib:Code Generation Library的缩写,依赖与ASM库。Spring和Hibernate选择了同样的cglib包。Hibernate主要利用cglib生成pojo的子类并用override get方法来实现lazy loading机制,Spring则是利用cglib来实现动态代理。JDK的动态代理必须得实现接口才行。

使用CGLib进行动态代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//普通类
public class HelloWorld{
public void savePerson() {
System.out.println("hello, world!");
}
}

//增强类
public class HelloWorldInterceptor implements MethodInterceptor{

// 被增强的类
private Object target;
// 如何增强方法的信息
private Strength strength;

public HelloWorldInterceptor(Object target, Strength strength) {
super();
this.target = target;
this.strength = strength;
}

/**
* 用来产生代理对象
*/
public Object createProxy(){
// 增强类
Enhancer enhancer = new Enhancer();
// 设置增强类的回调方法
enhancer.setCallback(this);
//设置代理类的父类
enhancer.setSuperclass(HelloWorld.class);
return enhancer.create();
}

// 这里相当于InvocationHandler接口中的invoke方法
public Object intercept(Object arg0, Method method, Object[] arg2,
MethodProxy arg3) throws Throwable {
this.strength.begin();
method.invoke(target, arg2);
this.strength.end();
return null;
}
}

//增强的方法

// 待增强的方法
public class Strength {
// 前增强
public void begin(){
System.out.println("begin......");
}
// 后增强
public void end(){
System.out.println("end.......");
}
}

//测试
/**
* 使用cglib产生的代理类,其代理类是目标类的子类
*/
public class TestHello {
@Test
public void testPersonDaoProxy(){
Object target = new HelloWorld();
Strength strength = new Strength();
HelloWorldInterceptor interceptor = new HelloWorldInterceptor(target, strength);
HelloWorld helloWorldProxy = (HelloWorld)interceptor.createProxy();
helloWorldProxy.savePerson();
}
}
源码分析

InvocationHandler源码

1
2
3
4
5
6
7
8
9
10
/**
* 每个代理类实例都有一个与之关联的invocation句柄。
* 当代理类的实例调用一个方法的时候,
* 该方法的invocation被编码并且被分发到它对应的invocation句柄
*/
public interface InvocationHandler {

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

Proxy源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
/**
* Proxy类提供了用于创建代理对象和实例的静态方法, 并且它也是所有通过这些静态方法创建出的代理类的父类。
*
* 从某个接口创建代理对象的方法如下:
*
* InvocationHandler handler = new MyInvocationHandler(...);
* Class proxyClass = Proxy.getProxyClass(
* Foo.class.getClassLoader(), new Class[] { Foo.class });
* Foo f = (Foo) proxyClass.
* getConstructor(new Class[] { InvocationHandler.class }).
* newInstance(new Object[] { handler });
* 或者有更简单的方法
* Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
* new Class[] { Foo.class }, handler);
*
* 一个动态代理类(以下简称为代理类)是该类被创建时在运行时动态
* 实现一系列接口的类,有如下的特性。
*
* 动态代理接口是被动态类实现的接口。代理实例是代理类一个一个实例。
*
* 每个代理实例都和一个实现了InvocationHandler接口的invocation handler对象关联。
* 通过代理接口进行的代理类实例的方法调用将会被分发给invocation handler实例,同时传递的
* 参数有代理实例,确定被调用方法的Method对象和Object数组类型的参数。
*/

// 代理对象是可以序列化的
public class Proxy implements java.io.Serializable {

private static final long serialVersionUID = -2222568056686623797L;

/** 为所有代理类的类名添加前缀,难怪hibernate中很多类都有$Proxy */
private final static String proxyClassNamePrefix = "$Proxy";

/** 代理类构造方法的参数类型 */
private final static Class[] constructorParams =
{ InvocationHandler.class };

/** 将代理类的类加载器放在Proxy的maps结构中缓存起来 */
/** 为啥这里用了WeakHashMap() */
private static Map<ClassLoader, Map<List<String>, Object>> loaderToCache
= new WeakHashMap<>();

/** 用于标记正在生成的代理类 */
private static Object pendingGenerationMarker = new Object();

/** 下一个用于生成唯一代理类名字的数字 */
private static long nextUniqueNumber = 0;
private static Object nextUniqueNumberLock = new Object();

/** 所有代理类的集合,用于isProxyClass方法的实现 */
private static Map<Class<?>, Void> proxyClasses =
Collections.synchronizedMap(new WeakHashMap<Class<?>, Void>());

/**
* 代理实例的invocationHandler对象,原来放在这儿啊.
* @serial
*/
protected InvocationHandler h;

/**
* 禁止实例化
*/
private Proxy() {
}

/**
* 从一个子类中创建代理类的实例(最典型的是一个动态代理类)
* 构造方法的参数是用来指定invocation handler
* 这个方法还是受保护的呢
*/
protected Proxy(InvocationHandler h) {
doNewInstanceCheck();
this.h = h;
}

/**
* 为什么不注释这个方法?
* 类名大致意思是代理访问助手?
* 这还是个静态的内部类
*/
private static class ProxyAccessHelper {
// The permission is implementation specific.
// 权限是指定实现的。
static final Permission PROXY_PERMISSION =
new ReflectPermission("proxyConstructorNewInstance");
// These system properties are defined to provide a short-term
// workaround if customers need to disable the new security checks.
// 这些系统配置被定义用来提供短期的配置,如果用户需要禁止新的安全检查。
static final boolean allowNewInstance;
static final boolean allowNullLoader;
static {
allowNewInstance = getBooleanProperty("sun.reflect.proxy.allowsNewInstance");
allowNullLoader = getBooleanProperty("sun.reflect.proxy.allowsNullLoader");
}

// 方法参数还可以这样修饰final?
private static boolean getBooleanProperty(final String key) {
String s = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty(key);
}
});
return Boolean.valueOf(s);
}

// 需要进行创建新实例的检查么
static boolean needsNewInstanceCheck(Class<?> proxyClass) {
if (!Proxy.isProxyClass(proxyClass) || allowNewInstance) {
return false;
}

if (ReflectUtil.isNonPublicProxyClass(proxyClass)) {
for (Class<?> intf : proxyClass.getInterfaces()) {
if (!Modifier.isPublic(intf.getModifiers())) {
return true;
}
}
}
return false;
}
}

/*
* Access check on a proxy class that implements any non-public interface.
*
* @throws SecurityException if a security manager exists, and
* the caller does not have the permission.
*/
private void doNewInstanceCheck() {
SecurityManager sm = System.getSecurityManager();
Class<?> proxyClass = this.getClass();
if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(proxyClass)) {
try {
sm.checkPermission(ProxyAccessHelper.PROXY_PERMISSION);
} catch (SecurityException e) {
throw new SecurityException("Not allowed to construct a Proxy "
+ "instance that implements a non-public interface", e);
}
}
}

/**
* 这个方法是重头了
* 从给定的类加载器和接口数组返回动态代理类的Class对象。
* Returns the {@code java.lang.Class} object for a proxy class
* given a class loader and an array of interfaces.
* 代理将会通过指定的类加载器所定义而且将会实现所提供的所有的接口。
*
* 如果已经有实现了相同接口的代理类存在,那么将会直接放回已经存在的代理类;
* 否则,将会动态地创建和由类加载器定义的代理类被返回。

* 对于传递给Proxy.getProxyClass方法的参数有若干限制。
*
* 在数组中所有的类类型必须是接口,不是其他的类类型或者原始类型。
* 接口数组中的接口必须两两不同
* 所有的接口类型都可以通过指定的类加载器按名字进行加载。
* 也就是对于数组中的每个接口i如下判断必须为true
* Class.forName(i.getName(), false, cl) == i
*
* 所有非公开的接口都必须在相同的包里面;不然的话,代理类不可能实现所有的接口,
* 不论它是在哪个包里面定义的。
*
* 对于指定接口的成员方法的集合的中的方法必须有相同的签名。
*
* 如果任何方法的返回类型是原始类型或者为空,那么所有的的方法都必须有相同的返回类型。
*
* 不然,其中一个方法必须可以对其他方法的返回类型是可赋值的。
*
* 生成的代理类不能超过虚拟机对其中的类的限制。例如,虚拟机会限制一个类的接口个数不超过65535;
* 因此传进去的接口的数组的长度不能超过65535
*
* 如果以上约束有违反的话,将会抛出IllegalArgumentException异常。
* 如果有接口是null的话,就会报NullPointerException异常。
*
* 注意到特定的代理接口的顺序是有意义的:对于创建具有相同代理接口的不同顺序的请求生成的代理类是不同的两个
* 代理类。(卧槽,这个也有影响,不读下源码一脸懵逼)
*/
@CallerSensitive
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
// 安全管理
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, interfaces);
}

// 返回代理对象
return getProxyClass0(loader, interfaces);
}

/*
* Check permissions required to create a Proxy class.
*
* To define a proxy class, it performs the access checks as in
* Class.forName (VM will invoke ClassLoader.checkPackageAccess):
* 1. "getClassLoader" permission check if loader == null
* 2. checkPackageAccess on the interfaces it implements
*
* To get a constructor and new instance of a proxy class, it performs
* the package access check on the interfaces it implements
* as in Class.getConstructor.
*
* If an interface is non-public, the proxy class must be defined by
* the defining loader of the interface. If the caller's class loader
* is not the same as the defining loader of the interface, the VM
* will throw IllegalAccessError when the generated proxy class is
* being defined via the defineClass0 method.
*/
private static void checkProxyAccess(Class<?> caller,
ClassLoader loader,
Class<?>... interfaces)
{
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader ccl = caller.getClassLoader();
if (loader == null && ccl != null) {
if (!ProxyAccessHelper.allowNullLoader) {
sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
ReflectUtil.checkProxyPackageAccess(ccl, interfaces);
}
}

/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {

// 判断接口的个数
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}

// 代理类
Class<?> proxyClass = null;

/* 接口名称的集合用来作为代理类的缓存 */
String[] interfaceNames = new String[interfaces.length];

// 检查接口是否有重复的
Set<Class<?>> interfaceSet = new HashSet<>();

for (int i = 0; i < interfaces.length; i++) {
/*
* 验证类加载器对接口名称的解析是同一个对象
* 因为不同类加载加载的类是不同的
*/

// 获取接口的名字
String interfaceName = interfaces[i].getName();
Class<?> interfaceClass = null;
try {
// 拿到接口的类类型
interfaceClass = Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
}

// 两个类类型之间的比较
if (interfaceClass != interfaces[i]) {
throw new IllegalArgumentException(
interfaces[i] + " is not visible from class loader");
}

/*
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}

/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.contains(interfaceClass)) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}

// 将接口加入到集合中
interfaceSet.add(interfaceClass);

interfaceNames[i] = interfaceName;
}

/*
* 使用字符串代表代理接口作为代理类缓存的主键(而不是它们的类对象)
* 已经足够了因为我们要求代理接口必须能根据给定的类加载器通过名字被解析,
* 而且还有一个好处是,使用字符串代表类弥补了对类的隐式弱引用。
*/

// 将数组转为字符串
List<String> key = Arrays.asList(interfaceNames);

/*
* 根据类加载器找到或者创建代理类的缓存.
*/
Map<List<String>, Object> cache;
synchronized (loaderToCache) {
cache = loaderToCache.get(loader);
if (cache == null) {
cache = new HashMap<>();
loaderToCache.put(loader, cache);
}
/*
* 这个映射在这个方法执行时一直有效,不用过多的同步,
* 因为仅仅会在类加载器不可达的时候被移除。
*/
}

/*
* Look up the list of interfaces in the proxy class cache using
* the key. This lookup will result in one of three possible
* kinds of values:
* null, if there is currently no proxy class for the list of
* interfaces in the class loader,
* the pendingGenerationMarker object, if a proxy class for the
* list of interfaces is currently being generated,
* or a weak reference to a Class object, if a proxy class for
* the list of interfaces has already been generated.
*/
synchronized (cache) {
/*
* Note that we need not worry about reaping the cache for
* entries with cleared weak references because if a proxy class
* has been garbage collected, its class loader will have been
* garbage collected as well, so the entire cache will be reaped
* from the loaderToCache map.
*/
do {
Object value = cache.get(key);
if (value instanceof Reference) {
proxyClass = (Class<?>) ((Reference) value).get();
}
if (proxyClass != null) {
// proxy class already generated: return it
return proxyClass;
} else if (value == pendingGenerationMarker) {
// proxy class being generated: wait for it
try {
cache.wait();
} catch (InterruptedException e) {
/*
* The class generation that we are waiting for should
* take a small, bounded time, so we can safely ignore
* thread interrupts here.
*/
}
continue;
} else {
/*
* No proxy class for this list of interfaces has been
* generated or is being generated, so we will go and
* generate it now. Mark it as pending generation.
*/
cache.put(key, pendingGenerationMarker);
break;
}
} while (true);
}

try {
String proxyPkg = null; // package to define proxy class in

/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
for (int i = 0; i < interfaces.length; i++) {
int flags = interfaces[i].getModifiers();
if (!Modifier.isPublic(flags)) {
String name = interfaces[i].getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}

if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}

{
/*
* Choose a name for the proxy class to generate.
*/
long num;
synchronized (nextUniqueNumberLock) {
num = nextUniqueNumber++;
}
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* Verify that the class loader hasn't already
* defined a class with the chosen name.
*/

/*
* 生成指定的代理类,可以看出这里的是二进制类型
* ProxyGenerator的源码看不到了
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
try {
// defineClass0是一个本地方法
proxyClass = defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
// 将其加入到所有生成的代理类的集合中,用于判断是否是代理类.
proxyClasses.put(proxyClass, null);

} finally {
/*
* We must clean up the "pending generation" state of the proxy
* class cache entry somehow. If a proxy class was successfully
* generated, store it in the cache (with a weak reference);
* otherwise, remove the reserved entry. In all cases, notify
* all waiters on reserved entries in this cache.
*/
synchronized (cache) {
if (proxyClass != null) {
cache.put(key, new WeakReference<Class<?>>(proxyClass));
} else {
cache.remove(key);
}
cache.notifyAll();
}
}

// 返回代理类
return proxyClass;
}

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
if (h == null) {
throw new NullPointerException();
}

// 安全检查
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, interfaces);
}

/*
* 查找或者生成指定的代理类
*/
Class<?> cl = getProxyClass0(loader, interfaces);

/*
* 调用指定了invocation handler的构造方法。
*/
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
// create proxy instance with doPrivilege as the proxy class may
// implement non-public interfaces that requires a special permission
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
return newInstance(cons, ih);
}
});
} else {
// 直接返回了代理对象
return newInstance(cons, ih);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
}
}

private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
try {
return cons.newInstance(new Object[] {h} );
} catch (IllegalAccessException | InstantiationException e) {
throw new InternalError(e.toString());
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString());
}
}
}

/**
* Returns true if and only if the specified class was dynamically
* generated to be a proxy class using the {@code getProxyClass}
* method or the {@code newProxyInstance} method.
*
* <p>The reliability of this method is important for the ability
* to use it to make security decisions, so its implementation should
* not just test if the class in question extends {@code Proxy}.
*
* @param cl the class to test
* @return {@code true} if the class is a proxy class and
* {@code false} otherwise
* @throws NullPointerException if {@code cl} is {@code null}
*/
public static boolean isProxyClass(Class<?> cl) {
if (cl == null) {
throw new NullPointerException();
}

return proxyClasses.containsKey(cl);
}

/**
* Returns the invocation handler for the specified proxy instance.
*
* @param proxy the proxy instance to return the invocation handler for
* @return the invocation handler for the proxy instance
* @throws IllegalArgumentException if the argument is not a
* proxy instance
*/
@CallerSensitive
public static InvocationHandler getInvocationHandler(Object proxy)
throws IllegalArgumentException
{
/*
* Verify that the object is actually a proxy instance.
*/
if (!isProxyClass(proxy.getClass())) {
throw new IllegalArgumentException("not a proxy instance");
}

final Proxy p = (Proxy) proxy;
final InvocationHandler ih = p.h;
if (System.getSecurityManager() != null) {
Class<?> ihClass = ih.getClass();
Class<?> caller = Reflection.getCallerClass();
if (ReflectUtil.needsPackageAccessCheck(caller.getClassLoader(),
ihClass.getClassLoader()))
{
ReflectUtil.checkPackageAccess(ihClass);
}
}

return ih;
}

private static native Class defineClass0(ClassLoader loader, String name,
byte[] b, int off, int len);
}
上一页1…171819…25下一页
初晨

初晨

永远不要说你知道本质,更别说真相了。

249 日志
46 分类
109 标签
近期文章
  • WebSocket、Socket、TCP、HTTP区别
  • Springboot项目的接口防刷
  • 深入理解Volatile关键字及其实现原理
  • 使用vscode搭建个人笔记环境
  • HBase介绍安装与操作
© 2018 — 2020 Copyright
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4