public class UserController extends HttpServlet {
private UserService userService;
…………
}
<bean id="userService" class="UserService">
…………
</bean>
<bean id="userController" class="UserController">
<property name="userService" ref="userService"/>
…………
</bean>
<!-- context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
public interface EmailService {
void sendEmail();
}
public class EmailServiceImpl implements EmailService {
public void sendEmail() {
System.out.println("==== Send email ====");
}
}
public interface UserService {
void register(String email, String password);
}
public class UserServiceImpl implements UserService {
private EmailService emailService;
// 通过 set 方法注入 emailService
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
public void register(String email, String password) {
System.out.println("==== Register user ====");
emailService.sendEmail();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 每个 bean 都有一个 id 标识,class 指向我们写的 bean 类全路径名 -->
<bean id="emailService" class="com.sfac.spring.service.impl.EmailServiceImpl" />
<bean id="userService" class="com.sfac.spring.service.impl.UserServiceImpl">
<!-- 通过 property 注入另外一个 bean -->
<property name="emailService" ref="emailService" />
</bean>
</beans>
EmailService emailService = new EmailServiceImpl();
UserService userService = new UserServiceImpl();
userService.setEmailService(emailService);
public class JavaSpringApplication {
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("application.xml");
UserService userService = applicationContext.getBean(UserServiceImpl.class);
userService.register("hujiang", "111111");
}
}
public class EmailServiceImpl implements EmailService {
}
public class UserServiceImpl implements UserService {
private EmailService emailService;
}
public class SpringIocTest {
public static void main(String[] args) {
// 通过读取xml的方式,ClassPath,对应到 target 下 classes 文件夹中的内容
// ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
// 通过注解的方式装配 Bean
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringIocTest.class);
UserService userService = applicationContext.getBean(UserService.class);
userService.register("admin1@163.com", "111111");
}
}
public class MailService {
…………
}
ConfigurableBeanFactory.SCOPE_PROTOTYPE) // @Scope("prototype") (
public class MailService {
…………
}
public class SpringIocTest {
// 指定 Bean 别名的方式二,@Bean + @Qualifier
//@Bean
//@Qualifier("utc8")
ZoneId zoneIdShanghai() {
return ZoneId.of("UTC+08:00");
}
// 指定 Bean 别名的方式一
"utc") (
ZoneId zoneIdBase() {
return ZoneId.of("UTC+00:00");
}
…………
}
// 按照 bean 类型注入,找不到则忽略
required = false) (
private ZoneId zoneId = ZoneId.systemDefault();
// 按照 bean 名字注入
name = "utc8") (
private ZoneId zoneIdUtc8;
public interface Validator {
void validate(String email, String password);
}
// @Order 为多个 Bean 设置优先级
1) (
public class EmailValidator implements Validator {
public void validate(String email, String password) {
if (!email.matches("^[a-z0-9]+\\@[a-z0-9]+\\.[a-z]{2,10}$")) {
throw new IllegalArgumentException("invalid email: " + email);
}
}
}
2) (
public class PasswordValidator implements Validator {
public void validate(String email, String password) {
if (!password.matches("^.{6,20}$")) {
throw new IllegalArgumentException("invalid password");
}
}
}
public class Validators {
private List<Validator> list;
public void validate(String email, String password) {
for (Validator validator : list) {
validator.validate(email, password);
}
}
}
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false
jdbc.username=root
jdbc.password=root
public class ConfigUtil {
"classpath:/jdbc-parms.properties") // classpath 方式 (
//@Value("file:/temp/jdbc-parms.properties") // 指定文件路径的方式
private Resource resource;
public Properties getProperties() {
Properties properties = new Properties();
try {
properties.load(resource.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
return properties;
}
}
Properties properties = configUtil.getProperties();
System.out.println(properties.getProperty("jdbc.url"));
"classpath:jdbc-parms.properties") (
public class JdbcParams {
// 如果 jdbc.driverClassName 不存在,则使用冒号后面的默认值,默认值无需加引号
"${jdbc.driverClassName:defaultValue}") (
private String driverClassName;
"${jdbc.url}") (
private String url;
"${jdbc.username}") (
private String userName;
"${jdbc.password}") (
private String password;
…………
}
// UserService
private JdbcParams jdbcParams;
System.out.println(jdbcParams.getDriverClassName());
"!dev") // 非 dev 环境创建 (
ZoneId zoneIdShanghai() {
return ZoneId.of("UTC+08:00");
}
"utc") (
"dev") // dev 环境创建 (
ZoneId zoneIdBase() {
return ZoneId.of("UTC+00:00");
}
//@Conditional(***.class)
public class ConfigUtil {
}
// 前三个阶段容纳到 AbstractAutowireCapableBeanFactory doCreateBean 方法中
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
// 实例化阶段
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 属性赋值阶段
populateBean(beanName, mbd, instanceWrapper);
// 初始化阶段
exposedObject = initializeBean(beanName, exposedObject, mbd);
} catch (Throwable ex) {
}
return exposedObject;
}
// AbstractAutowireCapableBeanFactory.class
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args)
throws BeanCreationException {
try {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
// postProcessBeforeInstantiation 方法调用点
// for循环调用所有的InstantiationAwareBeanPostProcessor
// 在 doCreateBean 方法之前调用
// 该方法的返回值会替换原本的Bean作为代理,这也是Aop等功能实现的关键点
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
} catch (Throwable ex) {
}
try {
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
} catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
}
}
// AbstractAutowireCapableBeanFactory.class
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
// postProcessAfterInstantiation 调用点
// 方法作为属性赋值的前置检查条件,在属性赋值之前执行
// 返回值为boolean,返回 false 时可以阻断属性赋值阶段
boolean continueWithPropertyPopulation = true;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
continueWithPropertyPopulation = false;
break;
}
}
}
}
}
// AbstractApplicationContext.class
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
// 所有 BeanPostProcesser 初始化的调用点
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
// 所有单例非懒加载 Bean 的调用点
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
}
// AbstractAutowireCapableBeanFactory.class
protected Object initializeBean(final String beanName, final Object bean,
RootBeanDefinition mbd) {
// 调用 Group1 中的 Aware
invokeAwareMethods(beanName, bean);
// 调用 Group2 中的 Aware
// 实质上这里就是前面所说的 BeanPostProcessor 的调用点
// 与 Group1 不同,是通过 BeanPostProcessor(ApplicationContextAwareProcessor)实现的
Object wrappedBean = bean;
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
// 下文即将介绍的 InitializingBean 调用点
invokeInitMethods(beanName, wrappedBean, mbd);
// BeanPostProcessor的另一个调用点
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
return wrappedBean;
}
public class TestService1 {
public TestService2 testService2;
public void test1() {
}
}
public class TestService2 {
private TestService1 testService1;
public void test2() {
}
}
public class UserService {
public void createUser(User user) {
securityCheck();
Transaction tx = startTransaction();
try {
// user相关业务
tx.commit();
} catch (RuntimeException e) {
tx.rollback();
throw e;
}
log("created user: " + user);
}
}
public class UserServiceProxy extends UserService {
private UserService target;
public User register(String email, String password) {
// do something like securityCheck, transaction, log
return target.register(email, password);
}
}
public class InvocationHandlerImpl<T> implements InvocationHandler {
private T target;
public InvocationHandlerImpl(T target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// do something like securityCheck, transaction, log
System.out.println("---------------");
Object result = method.invoke(target, args);
System.out.println("---------------");
return result;
}
public static void main(String[] args) {
// 创建被代理类
UserService target = new UserServiceImpl();
// 创建 InvocationHandler 动态代理类
InvocationHandlerImpl<UserService> invocationHandlerImpl = new InvocationHandlerImpl<UserService>(target);
// 创建代理对象
UserService userService = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class<?>[] {UserService.class},
invocationHandlerImpl);
// 代理对象调用方法,进行增强
userService.getUserByUserNameAndPassword("hyman", "111111");
}
}
<!-- Spring AOP -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- 支持切入点表达式 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
<!-- 支持 aop 注解 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
RetentionPolicy.RUNTIME) (
ElementType.METHOD) (
public @interface MethodInfoAnnotation {
}
public class MethodLoggerAspect {
private final static Logger LOGGER = LogManager.getLogger(MethodLoggerAspect.class);
private ObjectMapper mapper = new ObjectMapper();
/**
* -第一个*代表返回类型不限
* -第二个*代表 module 包下所有子包
* -第三个*代表 server 包下所有类
* -第四个*代表所有方法
* (..) 代表参数不限
* Order 代表优先级,数字越小优先级越高
* -多个表达式之间使用 ||, or 表示或 ,使用 && , and 表示与 , !表示非
*/
("execution(public * com.sfac.springMvc.module.*.service.*.*(..)) || "
+ "@annotation(com.sfac.springMvc.aspect.MethodInfoAnnotation)")
// @Pointcut("@annotation(com.sfac.springMvc.aspect.MethodInfoAnnotation)")
// @Pointcut("execution(public * com.sfac.springMvc.module.*.service.*.*(..))")
(1)
public void methodLoggerPointCut() {}
(value = "methodLoggerPointCut()")
public void beforeAspect(JoinPoint joinPoint) throws JsonProcessingException {
LOGGER.debug("======== Before Aspect ========");
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
// 有些参数无法 Json 序列化
//List<String> argsList = new ArrayList<String>();
//for (Object item : args) {
// argsList.add(mapper.writeValueAsString(item));
//}
LOGGER.debug(String.format("Call class: %s", className));
LOGGER.debug(String.format("Call method: %s", methodName));
if (args.length > 0) {
LOGGER.debug(String.format("Call method args: %s", args));
}
}
(value = "methodLoggerPointCut()")
public Object aroundAspect(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
long endTime = System.currentTimeMillis();
LOGGER.debug(String.format("Elapse time: %s millisecond", (endTime - startTime)));
return result;
}
(value = "methodLoggerPointCut()")
public void afterAspect(JoinPoint joinPoint) {
LOGGER.debug("======== After Aspect ========");
}
}
<tx:annotation-driven />
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sfac</groupId>
<artifactId>java_spring_mvc</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>java_spring_mvc Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<java.version>1.8</java.version>
<spring.version>5.2.0.RELEASE</spring.version>
<aspectj.version>1.9.5</aspectj.version>
<jackson.version>2.11.3</jackson.version>
<tomcat.version>9.0.36</tomcat.version>
</properties>
<dependencies>
<!-- Spring IOC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring AOP -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- 支持切入点表达式 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
<!-- 支持 aop 注解 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring Data Access -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Commons -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- 序列化 LocalDateTime 等时间类 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>java_spring_mvc</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
<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">
<display-name>Java Spring Mvc Application</display-name>
<!-- welcome page -->
<welcome-file-list>
<welcome-file>/index.jsp</welcome-file>
</welcome-file-list>
<!-- servlet 上下文 -->
<!-- 指定 spring 配置文件,启动 spring 容器 -->
<context-param>
<!-- 启动容器就会加载此处配置的配置文件
第一种写法,配置文件放在 src/main/resources 下
第二种写法,配置文件放在 /WEB-INF/conf/spring 下
-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/applicationContext.xml</param-value>
<!-- <param-value>/WEB-INF/config/applicationContext.xml</param-value> -->
</context-param>
<!-- listener -->
<!-- spring 监听器:启动 Web 容器时,自动装配 ApplicationContext 的配置信息 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- filter -->
<!-- 字符过滤器 -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- servlet -->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/springMvcConfig.xml</param-value>
</init-param>
<!-- 值大于等于0表示容器启动应用时候加载该servlet,数值越小优先级越高 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- 扫描构造型(stereotype)注解所标注的类,@Component、@Repository、@Service、@Controller 等 -->
<context:component-scan base-package="com.sfac.springMvc">
<!-- 设置黑名单,不扫描 Controller -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- 扫描构造型(stereotype)注解所标注的类,@Component、@Repository、@Service、@Controller 等 -->
<context:component-scan base-package="com.sfac.springMvc" use-default-filters="false">
<!-- 设置白名单,只扫描 Controller -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 启用注解驱动:将扫描到的组件注册到工厂中,来处理请求;
Spring 3.0.x 新增配置,其中最主要的两个类:DefaultAnnotationHandlerMapping 和 AnnotationMethodHandlerAdapter,
分别为 HandlerMapping 的实现类和 HandlerAdapter 的实现类,
从 3.1.x 版本开始对应实现类改为了 RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter;
RequestMappingHandlerMapping:处理 @RequestMapping 注解,并将其注册到请求映射表中;
RequestMappingHandlerAdapter:处理请求的适配器,确定调用哪个类的哪个方法;
-->
<mvc:annotation-driven />
<!-- 处理静态资源方式一
Spring 3.0.x 新增 REST 风格,URL 不希望带 .html 或 .do 等后缀,在 web.xml 中配置 DispatcherServlet
的请求映射为 /,Spring MVC 将捕获 Web 容器所有的请求,包括静态资源的请求,但静态资源没有处理器,因此会报错;
该配置会在 Spring MVC 上下文中定义一个 DefaultServletHttpRequestHandler,它会像一个检查员,
对进入 DispatcherServlet 的 URL 进行筛查,如果是静态资源的请求,就将该请求转由 Web 服务器默认的 Servlet 处理,
如果不是静态资源的请求,才由 DispatcherServlet 继续处理;
一般 Web 服务器默认的 Servlet 名称是 "default",因此 DefaultServletHttpRequestHandler 可以找到它,如果你所有的 Web
服务器的默认 Servlet 名称不是 "default",则需要通过 default-servlet-name 属性显示指定;
该配置从项目 webapp 根目录开始计算路径,假设 webapp 下有 static 文件夹放置 css 和 js,
那么访问路径类似于:http://127.0.0.1/static/js/custom.js;
-->
<mvc:default-servlet-handler />
<!-- 处理静态资源方式二
该配置在处理静态资源上更进一步,由 Spring MVC 框架自己处理静态资源;
location:没有前缀,从项目 webapp 根目录开始计算路径,
访问路径 = 映射路径 + 相对路径,类似于:http://127.0.0.1/static/js/custom.js;
location: 添加 ‘file:’ 前缀,表示用本地文件夹作为静态资源文件夹,
访问路径 = 映射路径 + 相对路径,类似于:http://127.0.0.1/upload/test.jpg;
-->
<!-- <mvc:resources location="/static/" mapping="/**" /> -->
<mvc:resources location="file:D:/upload/" mapping="/upload/**" />
</beans>
"/test") (
public class TestController {
/**
* 127.0.0.1/test/desc ---- get
*/
("/desc")
public String testDesc() {
return "This is test module desc.";
}
}
<!-- Server runtime -->
<!-- Tomcat -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>
public class JavaSpringMvcApplication {
private static int PORT = 80;
private static String CONTEXT_PATH = "/";
public static void main(String[] args) throws LifecycleException {
// String baseDir = Thread.currentThread().getContextClassLoader().getResource("").getPath();
String baseDir = new File("target/classes").getAbsolutePath();
String appDir = new File("src/main/webapp").getAbsolutePath();
System.out.println(baseDir);
System.out.println(appDir);
// 创建 Tomcat,并设置基础目录、端口、连接器
Tomcat tomcat = new Tomcat();
tomcat.setBaseDir(baseDir);
tomcat.setPort(PORT);
tomcat.getConnector();
// 添加 webapp
Context context = tomcat.addWebapp(CONTEXT_PATH, appDir);
WebResourceRoot resources = new StandardRoot(context);
resources.addPreResources(new DirResourceSet(resources, "/WEB-INF/classes", baseDir, "/"));
context.setResources(resources);
// 启动服务器
tomcat.start();
tomcat.getServer().await();
}
}
<!-- Log4j -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>${log4j.version}</version>
</dependency>
<Configuration status="OFF" monitorInterval="1800">
<!-- 配置常量 -->
<properties>
<property name="env">dev</property>
<property name="project">java_spring_mvc</property>
<property name="root">/log</property>
<property name="fileSize">50MB</property>
<property name="maxHistory">100</property>
<property name="dateTime">yyyy-MM-dd HH:mm:ss</property>
<property name="pattern">%d{${dateTime}} [%t] %-5level %logger{36} - %msg%n</property>
</properties>
<!-- 配置控制台输出和文件输出 -->
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${pattern}" />
</Console>
<RollingFile name="running-log"
fileName="${root}/${project}_${env}.log"
filePattern="${root}/${project}_${env}_%d.%i.log"
immediateFlush="true">
<PatternLayout pattern="${pattern}" />
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="${fileSize}" />
</Policies>
<DefaultRolloverStrategy max="${maxHistory}" />
</RollingFile>
</Appenders>
<Loggers>
<!-- 指定第三方 jar 或自定义包的输出级别 -->
<Logger name="org.springframework" level="info" additivity="true">
<AppenderRef ref="Console" />
<AppenderRef ref="running-log" />
</Logger>
<!-- 指定根日志级别TRACE<DEBUG<INFO<WARN<ERROR -->
<Root level="debug">
<AppenderRef ref="Console" />
<AppenderRef ref="running-log" />
</Root>
</Loggers>
</Configuration>
<!-- 指定log4j配置文件 -->
<context-param>
<param-name>log4jConfiguration</param-name>
<param-value>classpath:config/log4j.xml</param-value>
</context-param>
<!-- Log4j监听器 -->
<listener>
<listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class>
</listener>
<!-- Log4j过滤器 -->
<filter>
<filter-name>log4jServletFilter</filter-name>
<filter-class>org.apache.logging.log4j.web.Log4jServletFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>log4jServletFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Description: Test Controller
* @author HymanHu
* @date 2020-12-08 10:10:23
*/
"/test") (
public class TestController {
private final static Logger LOGGER = LogManager.getLogger(TestController.class);
/**
* 127.0.0.1/test/logger ---- get
*/
("/logger")
public String loggerTest() {
LOGGER.trace("This is trace logger.");
LOGGER.debug("This is debug logger.");
LOGGER.info("This is info logger.");
LOGGER.warn("This is warn logger.");
LOGGER.error("This is error logger.");
return "This is logger test.";
}
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String uri = request.getRequestURI();
if (uri.startsWith("/upload")) {
request.getRequestDispatcher(URLDecoder.decode(uri, "utf-8")).forward(request, response);
} else {
filterChain.doFilter(request, response);
}
}
spring.resource.path=/upload/
spring.resource.path.pattern=${spring.resource.path}**
spring.resource.folder.windows=d:/upload/springMvc/
spring.resource.folder.linux=/upload/springMvc/
<!-- 加载属性文件 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:config/properties/springMvcParms.properties</value>
</list>
</property>
</bean>
<!-- 处理静态资源方式二
该配置在处理静态资源上更进一步,由 Spring MVC 框架自己处理静态资源;
location:没有前缀,从项目 webapp 根目录开始计算路径,
访问路径 = 映射路径 + 相对路径,类似于:http://127.0.0.1/static/js/custom.js;
location: 添加 ‘file:’ 前缀,表示用本地文件夹作为静态资源文件夹,
访问路径 = 映射路径 + 相对路径,类似于:http://127.0.0.1/upload/test.jpg;
-->
<!-- <mvc:resources location="/static/" mapping="/**" /> -->
<!-- <mvc:resources location="file:d:/upload/springMvc/" mapping="/upload/**" /> -->
<mvc:resources location="file:${spring.resource.folder.windows}" mapping="${spring.resource.path.pattern}" />
<!-- 加载属性文件 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:config/properties/jdbcParms.properties</value>
<value>classpath:config/properties/hibernateParms.properties</value>
<value>classpath:config/properties/springMvcParms.properties</value>
</list>
</property>
</bean>
"classpath:config/properties/springMvcParms.properties") (
public class ResourceConfigBean {
("${spring.resource.path}")
private String resourcePath;
("${spring.resource.path.pattern}")
private String resourcePathPattern;
("${spring.resource.folder.windows}")
private String localPathForWindow;
("${spring.resource.folder.linux}")
private String localPathForLinux;
// 自行添加 get、set方法
}
private ResourceConfigBean resourceConfigBean;
/**
* 127.0.0.1/test/config ---- get
*/
"/config") (
public ResourceConfigBean configTest() {
return resourceConfigBean;
}
public class Result<T> {
private int status;
private String message;
private T data;
public Result() {
}
public Result(int status, String message) {
this.status = status;
this.message = message;
}
public Result(int status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
// 自行添加 get、set方法
/**
* Description: Result Status
* @author HymanHu
* @date 2020-12-09 16:06:12
*/
public enum ResultStatus {
SUCCESS(200), FAILED(500);
public int code;
private ResultStatus(int code) {
this.code = code;
}
}
}
public class Search {
private int currentPage;
private int pageSize;
private String sort;
private String direction;
private String keyword;
public void init() {
this.currentPage = this.currentPage == 0 ? 1 : this.currentPage;
this.pageSize = this.pageSize == 0 ? 5 : this.pageSize;
this.sort = StringUtils.isBlank(this.sort) ? "id" : this.sort;
this.direction = StringUtils.isBlank(this.direction) ? "ASC" : this.direction;
}
}
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
技术 | 填充参数 | 映射结果集 | 查询语句 | 缓存 | 特点 |
JdbcTemplate | 手动 | 手动 | 手动 | ||
Mybatis | 自动 | 自动 | 手动 | 一二级缓存 | |
Hibernate | 自动 | 自动 | 自动 | 一二级缓存 | Hql 转 Sql 多了性能开销; Sql 不够灵活,优化麻烦; 缓存增大了数据的不一致性; |
Jpa | 自动 | 自动 | 自动 | 一二级缓存 | 引用是 javax.persistence 这个“标准”包,和 org.hibernate 实现包; |
<!-- Mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
<!-- <version>8.0.18</version> -->
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.4</version>
</dependency>
<!-- Mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!-- Mybatis 处理 LocalDatetime -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-typehandlers-jsr310</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.1.6</version>
</dependency>
<!-- mybatis generator -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.7</version>
</dependency>
<!-- 加载属性文件 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:config/properties/jdbcParms.properties</value>
</list>
</property>
</bean>
<!-- 引入别的配置文件 -->
<import resource="springMybatis.xml"/>
#jdbc.driverClassName=com.mysql.jdbc.Driver
#jdbc.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=root
#连接池中连接用完时,c3p0一次性创建的连接数
pool.acquireIncrement=5
#初始化连接数,在minPoolSize和maxPoolSize之间
pool.initialPoolSize=5
pool.minPoolSize=5
pool.maxPoolSize=200
#连接关闭时默认将所有未提交的操作回滚,默认为false
pool.autoCommitOnClose=true
<configuration>
<settings>
<!-- 打印 sql 语句,方式:SLF4J,LOG4J,LOG4J2,JDK_LOGGING,COMMONS_LOGGING,STDOUT_LOGGING,NO_LOGGING -->
<setting name="logImpl" value="STDOUT_LOGGING" />
<!-- 开启驼峰映射 -->
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
<plugins>
<!-- 配置分页插件 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
</configuration>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- 数据源,spring jdbc || c3p0 jdbc -->
<!-- <bean id="dataSourceForMybatis" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean> -->
<!-- 配置 c3p0 数据源 -->
<bean id="dataSourceForMybatis" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClassName}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 连接池中连接用完时,c3p0 一次性创建的连接数 -->
<property name="acquireIncrement" value="${pool.acquireIncrement}" />
<!-- 初始化连接数,在 minPoolSize 和 maxPoolSize 之间 -->
<property name="initialPoolSize" value="${pool.initialPoolSize}" />
<property name="minPoolSize" value="${pool.minPoolSize}" />
<property name="maxPoolSize" value="${pool.maxPoolSize}" />
<!-- 连接关闭时默认将所有未提交的操作回滚,默认为 false -->
<property name="autoCommitOnClose" value="${pool.autoCommitOnClose}"/>
</bean>
<!-- sqlSessionFactory 单个数据库映射关系经过编译后的内存镜像 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入 dataSource -->
<property name="dataSource" ref="dataSourceForMybatis"/>
<!-- 扫描 Mybatis 配置文件 -->
<property name="configLocation" value="classpath:config/mybatisConfig.xml"></property>
<!-- 扫描 *Mapper.xml 文件 -->
<property name="mapperLocations" value="classpath*:mybatis/*.xml" />
</bean>
<!-- 配置一个可以执行批量的 sqlSessionTemplate -->
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
<constructor-arg name="executorType" value="BATCH"/>
</bean>
<!-- 开启自动扫描 dao 接口 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="com.sfac.springMvc.module.*.dao" />
</bean>
<!-- 配置事务管理器 -->
<bean id="mybatisTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceForMybatis"/>
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="mybatisTransactionManager" />
</beans>
<generatorConfiguration>
<context id="DB2Tables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 -->
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!-- Mysql数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false"
userId="root"
password="root">
</jdbcConnection>
<!-- Oracle数据库
<jdbcConnection driverClass="oracle.jdbc.OracleDriver"
connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg"
userId="yycg"
password="yycg">
</jdbcConnection>
-->
<!-- 默认为false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true 时
把 JDBC DECIMAL 和 NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成 POJO 类的位置 -->
<javaModelGenerator targetPackage="com.sfac.springMvc.module.test.entity" targetProject="./src/main/java">
<!-- enableSubPackages: 是否让 schema 作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper 映射文件生成的位置 -->
<sqlMapGenerator targetPackage="mybatis" targetProject="./src/main/resources">
<!-- enableSubPackages:是否让 schema 作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetProject:mapper 接口生成的的位置 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.sfac.springMvc.module.test.dao"
targetProject="./src/main/java">
<!-- enableSubPackages: 是否让 schema 作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定数据表 -->
<table schema="" tableName="test_city"></table>
<table schema="" tableName="test_country"></table>
<!-- 有些表的字段需要指定java类型
<table schema="DB2ADMIN" tableName="ALLTYPES" domainObjectName="Customer" >
<property name="useActualColumnNames" value="true"/>
<generatedKey column="ID" sqlStatement="DB2" identity="true" />
<columnOverride column="DATE_FIELD" property="startDate" />
<ignoreColumn column="FRED" />
<columnOverride column="LONG_VARCHAR_FIELD" jdbcType="VARCHAR" />
</table> -->
</context>
</generatorConfiguration>
public class MybatisGeneratorSqlMap {
public void generator() throws Exception {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
// 指定配置文件
File configFile = new File(
MybatisGeneratorSqlMap.class.getResource("/config/mybatisGeneratorConfig.xml").getPath());
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
}
// 执行main方法以生成代码
public static void main(String[] args) {
try {
MybatisGeneratorSqlMap generatorSqlmap = new MybatisGeneratorSqlMap();
generatorSqlmap.generator();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public interface CityDao {
// 要使用 useGeneratedKeys,要求参数必须包含生成的主键,否则抛出异常
// Error getting generated key or setting result to parameter object
"insert into test_city (city_id, city_name, local_city_name, country_id, district, population, " (
+ "date_modified, date_created) values (#{cityId}, #{cityName}, #{localCityName}, "
+ "#{countryId}, #{district}, #{population}, #{dateModified}, #{dateCreated})")
(useGeneratedKeys = true, keyColumn = "city_id", keyProperty = "cityId")
void insertCity(City city);
}
public class CityServiceImpl implements CityService {
private CityMapper cityMapper;
private CityDao cityDao;
public ResultEntity<City> insertCity(City city) {
city.setDateCreated(new Date());
/*
* -实现方式一:*Mapper.xml + *Mapper.java 接口
* -如果使用逆向工程生成的 Mapper.xml 文件,无法返回主键值,需要修改 Mapper.xml 中的 insert 方法;
* <insert id="insert" parameterType="com.sfac.springMvc.entity.City" useGeneratedKeys="true" keyProperty="cityId">
*/
// cityMapper.insert(city);
/*
* -实现方式二:纯接口 + 注解开发
*/
cityDao.insertCity(city);
return new ResultEntity<City>(ResultStatus.SUCCESS.status, "Insert city success.", city);
}
}
"/api") (
public class CityController {
private CityService cityService;
/**
* 127.0.0.1/api/city ---- post
* {"cityName":"dreamCity", "localCityName":"梦想城市", "countryId":522, "district":"sichuan", "population":1780000}
*/
(value = "/city", consumes = "application/json")
public ResultEntity<City> insertCity( City city) {
return cityService.insertCity(city);
}
}
"update test_city set city_name = #{cityName}, local_city_name = #{localCityName}, " (
+ "country_id = #{countryId}, district = #{district}, population = #{population}, "
+ "date_modified = #{dateModified} where city_id = #{cityId}")
void updateCity(City city);
public ResultEntity<City> updateCity(City city) {
city.setDateModified(new Date());
cityDao.updateCity(city);
int i = 1 / 0;
return new ResultEntity<City>(ResultStatus.SUCCESS.status, "Update city success.", city);
}
/**
* 127.0.0.1/api/city ---- put
* cityId=2261,cityName=dreamCity,localCityName=梦想城市1,
* countryId=522,district=sichuan,population=1780001
*/
value = "/city", consumes = "application/x-www-form-urlencoded") (
public ResultEntity<City> updateCity( City city) {
return cityService.updateCity(city);
}
<!-- 解决 PUT 请求无法提交表单数据的问题 -->
<filter>
<filter-name>httpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>httpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 解决 Form 表单不支持 Put、Delete 请求问题,页面使用 _method 隐藏域指定真正的请求类型 -->
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<!-- 绑定 DispatcherServlet -->
<servlet-name>springMVC</servlet-name>
</filter-mapping>
"delete from test_city where city_id = #{cityId}") (
void deleteCityByCityId(int cityId);
public ResultEntity<Object> deleteCityByCityId(int cityId) {
cityDao.deleteCityByCityId(cityId);
return new ResultEntity<Object>(ResultStatus.SUCCESS.status, "Delete city success.");
}
/**
* 127.0.0.1/api/city/2260 ---- delete
*/
"/city/{cityId}") (
public ResultEntity<Object> deleteCityByCityId( int cityId) {
return cityService.deleteCityByCityId(cityId);
}
"select ci.*, co.country_name from test_city ci " (
+ "left join country co on ci.country_id = co.country_id "
+ "where ci.city_id = #{cityId}")
City getCityByCityId(int cityId);
public City getCityByCityId(int cityId) {
return cityDao.getCityByCityId(cityId);
}
/**
* 127.0.0.1/api/city/2261 ---- get
*/
"/city/{cityId}") (
public City getCityByCityId( int cityId) {
return cityService.getCityByCityId(cityId);
}
"select * from test_city where country_id = #{countryId}") (
List<City> getCitiesByCountryId(int countryId);
public interface CountryDao {
/**
* @Results ---- 封装结果集,对于联表查询的字段,可调用已有的方法 getCitiesByCountryId
* column ---- 对应 select 查询后的某个字段名,作为映射实体 bean 属性或者作为调用方法的参数
* property ---- 对应 实体 bean 属性
* 1、country_id 封装了两次,分别对应 countryId 和 cities,
* -而 cities 属性通过 getCitiesByCountryId 方法来实现,country_id作为参数
* 2、结果集共享,设置 id 属性,调用时使用 @ResultMap(value="countryResults")
*/
("select * from test_country where country_id = #{countryId}")
(id = "countryResults", value = {
(column = "country_id", property = "countryId"),
(column = "country_id", property = "cities",
javaType = List.class,
many = (select = "com.sfac.springMvc.dao.CityDao.getCitiesByCountryId")
)
})
Country getCountryByCountryId(int countryId);
("select * from test_country where country_name = #{countryName}")
(value = "countryResults")
Country getCountryByCountryName(String countryName);
}
public class CountryServiceImpl implements CountryService {
private CountryDao countryDao;
public Country getCountryByCountryId(int countryId) {
return countryDao.getCountryByCountryId(countryId);
}
public Country getCountryByCountryName(String countryName) {
return countryDao.getCountryByCountryName(countryName);
}
}
"/api") (
public class CountryController {
private CountryService countryService;
/**
* 127.0.0.1/api/country/522 ---- get
*/
("/country/{countryId}")
public Country getCountryByCountryId( int countryId) {
return countryService.getCountryByCountryId(countryId);
}
/**
* 127.0.0.1/api/country?countryName=China ---- get
*/
("/country")
public Country getCountryByCountryName( String countryName) {
return countryService.getCountryByCountryName(countryName);
}
}
public class SearchBean {
public final static int DEFAULT_CURRENT_PAGE = 1;
public final static int DEFAULT_PAGE_SIZE = 5;
private int currentPage;
private int pageSize;
private String keyWord;
private String orderBy;
private String direction;
// 初始化 search bean
public void initSearchBean() {
if (this != null) {
this.setCurrentPage(this.getCurrentPage() == 0 ? DEFAULT_CURRENT_PAGE : this.getCurrentPage());
this.setPageSize(this.getPageSize() == 0 ? DEFAULT_PAGE_SIZE : this.getPageSize());
this.setDirection(StringUtils.isBlank(this.getDirection()) ? "asc" : this.getDirection());
}
}
// 自行添加 get、set 方法
}
"<script>" (
+ "select ci.*, co.country_name from test_city ci left join country co "
+ "on ci.country_id = co.country_id "
+ "<where> "
+ "<if test='keyWord != \"\" and keyWord != null'>"
+ " and (ci.city_name like '%${keyWord}%' "
+ "or ci.local_city_name like '%${keyWord}%' "
+ "or co.country_name like '%${keyWord}%') "
+ "</if>"
+ "</where>"
+ "<choose>"
+ "<when test='orderBy != \"\" and orderBy != null'>"
+ " order by ${orderBy} ${direction}"
+ "</when>"
+ "<otherwise>"
+ " order by city_id desc"
+ "</otherwise>"
+ "</choose>"
+ "</script>")
List<City> getCitiesBySearchBean(SearchBean searchBean);
"<script>" (
+ "select * from common_video "
+ "<where> "
+ " and (grade <= #{grade} ) "
+ "<if test='type != \"\" and type != null'>"
+ " and (type = #{type}) "
+ "</if>"
+ "<if test='search.keyword != \"\" and search.keyword != null'>"
+ " and (name like '%${search.keyword}%' or subject like '%${search.keyword}%' or "
+ " number like '%${search.keyword}%' or star like '%${search.keyword}%' or imdb like '%${search.keyword}%') "
+ "</if>"
+ "</where>"
+ "<choose>"
+ "<when test='search.sort != \"\" and search.sort != null'>"
+ " order by ${search.sort} ${search.direction}"
+ "</when>"
+ "<otherwise>"
+ " order by id desc"
+ "</otherwise>"
+ "</choose>"
+ "</script>")
List<Video> getVideosBySearchAndTypeAndGrade(
("search") Search search,
("grade") int grade,
("type") String type);
public PageInfo<City> getCitiesBySearchBean(SearchBean searchBean) {
searchBean.initSearchBean();
PageHelper.startPage(searchBean.getCurrentPage(), searchBean.getPageSize());
return new PageInfo<City>(Optional.ofNullable(cityDao.getCitiesBySearchBean(searchBean))
.orElse(Collections.emptyList()));
}
/**
* 127.0.0.1/api/cities ---- post
* {"currentPage":1, "pageSize":5, "keyWord":"china", "orderBy":"ci.city_id", "direction":"desc"}
*/
value = "/cities", consumes = "application/json") (
public PageInfo<City> getCitiesBySearchBean( SearchBean searchBean) {
return cityService.getCitiesBySearchBean(searchBean);
}
"<script>" (
+ "insert into test_city (city_id, city_name, local_city_name, country_id, district, "
+ "population, date_modified, date_created) values "
+ "<foreach collection='cities' item='city' index='index' separator=','>"
+ "(#{city.cityId}, #{city.cityName}, #{city.localCityName}, #{city.countryId}, "
+ "#{city.district}, #{city.population}, #{city.dateModified}, #{city.dateCreated})"
+ "</foreach>"
+ "</script>")
useGeneratedKeys = true, keyColumn = "city_id", keyProperty = "cityId") (
void batchInsertCities( ("cities") List<City> cities);
public ResultEntity<List<City>> batchInsertCities(List<City> cities) {
cities.forEach(item -> {
item.setDateCreated(new Date());
});
cityDao.batchInsertCities(cities);
return new ResultEntity<List<City>>(ResultStatus.SUCCESS.status, "Insert city success.", cities);
}
/**
* 127.0.0.1/api/cities/v2 ---- post
* [{"cityName":"dreamCity1", "localCityName":"梦想城市1", "countryId":522, "district":"sichuan",
* "population":1780000},{"cityName":"dreamCity2", "localCityName":"梦想城市2", "countryId":522,
* "district":"sichuan", "population":1780000}]
*/
value = "/cities/v2", consumes = "application/json") (
public ResultEntity<List<City>> batchInsertCities( List<City> cities) {
return cityService.batchInsertCities(cities);
}
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.25.Final</version>
</dependency>
<!-- 加载属性文件 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:config/properties/jdbcParms.properties</value>
<value>classpath:config/properties/hibernateParms.properties</value>
</list>
</property>
</bean>
<!-- 引入别的配置文件 -->
<import resource="springHibernate.xml"/>
hibernate.hbm2ddl.auto=update
hibernate.implicit_naming_strategy=com.sfac.springMvc.config.HibernateNamingStrategy
hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.connection.autocommit=true
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- 数据源,spring jdbc || c3p0 jdbc -->
<!-- <bean id="dataSourceForHibernate" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean> -->
<!-- 配置 c3p0 数据源 -->
<bean id="dataSourceForHibernate" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClassName}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 连接池中连接用完时,c3p0 一次性创建的连接数 -->
<property name="acquireIncrement" value="${pool.acquireIncrement}" />
<!-- 初始化连接数,在 minPoolSize 和 maxPoolSize 之间 -->
<property name="initialPoolSize" value="${pool.initialPoolSize}" />
<property name="minPoolSize" value="${pool.minPoolSize}" />
<property name="maxPoolSize" value="${pool.maxPoolSize}" />
<!-- 连接关闭时默认将所有未提交的操作回滚,默认为 false -->
<property name="autoCommitOnClose" value="${pool.autoCommitOnClose}"/>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSourceForHibernate" />
<!-- 扫描 Entity 包 -->
<property name="packagesToScan">
<list>
<value>com.sfac.springMvc.module.*.entity</value>
</list>
</property>
<!-- 命名规则隐式策略 -->
<property name="implicitNamingStrategy">
<!-- 采用自定义配置 -->
<bean class="com.sfac.springMvc.config.HibernateNamingStrategy"></bean>
</property>
<property name="hibernateProperties">
<props>
<!-- 动态生成表策略 -->
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
<!-- 数据库方言 -->
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
</props>
</property>
</bean>
<!-- 引入hibernateTemplate -->
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate5.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory" />
<property name="checkWriteOperations" value="false"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="hibernateTransactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="hibernateTransactionManager"/>
</beans>
public class AbstractEntity {
(strategy = GenerationType.IDENTITY)
(nullable = false, updatable = false)
private Integer id;
(using = LocalDateTimeSerializer.class)
(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
(updatable = false)
private LocalDateTime createDate;
// 在 insert 之前,该方法被调用
public void preInsert() {
setCreateDate(LocalDateTime.now());
}
}
name = "test_student") (
public class Student extends AbstractEntity {
private String studentName;
/**
* OneToOne:一对一关系中,有外键方使用 JoinColumn 注解,另一方使用 mappedBy 属性(可选)
* targetEntity:目标实体
* cascade:联级操作
* fetch:加载数据策略
* JoinColumn
* name 对应 test_student 表中 card_id 外键列
*/
(targetEntity = Card.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
(name = "card_id", unique = true)
private Card studentCard;
/**
* ManyToMany,一方使用 JoinTable 注解,另一方配置 mappedBy 属性
* cascade:联级操作
* fetch:加载数据策略
* JsonIgnore:不序列化该字段,避免无限递归
*/
(mappedBy = "students", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Clazz> clazzes;
}
name = "test_card") (
public class Card extends AbstractEntity {
(name = "card_no", length = 33, unique = true)
private String cardNo;
/**
* OneToOne:一对一关系中,有外键方使用 JoinColumn 注解,另一方使用 mappedBy 属性(可选)
* cascade:联级操作
* fetch:加载数据策略
* JsonIgnore:不序列化该字段,避免无限递归
*/
(mappedBy = "studentCard", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Student student;
}
name = "test_clazz") (
public class Clazz extends AbstractEntity {
private String clazzName;
/**
* ManyToMany,一方使用 JoinTable 注解,另一方配置 mappedBy 属性
* cascade:联级操作
* fetch:加载数据策略
* JoinTable:中间表
* name: 中间表表名
* joinColumns:表 test_clazz 参与中间表的主键
* inverseJoinColumns:关联表 test_student 参与中间表的主键
*/
(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
(name = "test_clazz_student",
joinColumns = (name = "clazz_id"),
inverseJoinColumns = (name="student_id"))
private List<Student> students;
/**
* ManyToOne:多方使用 joinClumn,创建外键,一方使用 mappedBy 属性
* cascade:联级操作
* fetch:加载数据策略
* JoinColumn
* name:多方 test_clazz 表的外键 school_id
* insertable & updatable:标识该属性是否参与插入和更新插入
* JsonIgnore:不序列化该字段,避免无限递归
*/
(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
(name = "school_id", insertable = false, updatable = false)
private School school;
}
name = "test_school") (
public class School extends AbstractEntity {
private String schoolName;
// 定义数据库 text 类型,存储 64 KB 数据
columnDefinition="text") (
private String description;
private String region;
/**
* OneToMany:多方使用 joinClumn,创建外键,一方使用 mappedBy 属性
* cascade:联级操作
* fetch:加载数据策略
*/
(mappedBy = "school", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Clazz> clazzes;
}
public class StudentServiceImpl implements StudentService {
private HibernateTemplate hibernateTemplate;
("hibernateTransactionManager")
public ResultEntity<Student> insertStudentForHibernate(Student student) {
hibernateTemplate.saveOrUpdate(student);
return new ResultEntity<Student>(ResultStatus.SUCCESS.status, "Insert success.", student);
}
}
"/api") (
public class StudentController {
private StudentService studentService;
/**
* 127.0.0.1/api/hibernate/student ---- post
* {"studentName":"HymanHu"}
* {"studentName":"HymanHu", "studentCard":{"cardNo":"studentCard001"}}
*/
(value = "/hibernate/student", consumes = "application/json")
public ResultEntity<Student> insertStudentForHibernate( Student student) {
return studentService.insertStudentForHibernate(student);
}
}
// 系统中配置又多个事务管理器,所以需要在调用的时候需要指明
"hibernateTransactionManager") (
public ResultEntity<Student> updateStudentForHibernate(Student student) {
hibernateTemplate.saveOrUpdate(student);
// int i = 1 / 0;
return new ResultEntity<Student>(ResultStatus.SUCCESS.status, "Update success.", student);
}
/**
* 127.0.0.1/api/hibernate/student ---- put
* {"id":"2","studentName":"HymanHu1"}
* {"id":"2","studentName":"HymanHu1","studentCard":{"id":"1","cardNo":"studentCard002"}}
*/
value = "/hibernate/student", consumes = "application/json") (
public ResultEntity<Student> updateStudentForHibernate( Student student) {
return studentService.updateStudentForHibernate(student);
}
"hibernateTransactionManager") (
public ResultEntity<Object> deleteStudentForHibernate(Integer id) {
Student student = new Student();
student.setId(id);
hibernateTemplate.delete(student);
return new ResultEntity<Object>(ResultStatus.SUCCESS.status, "Delete success.");
}
/**
* 127.0.0.1/api/hibernate/student/2 ---- delete
*/
"/hibernate/student/{id}") (
public ResultEntity<Object> deleteStudentForHibernate( Integer id) {
return studentService.deleteStudentForHibernate(id);
}
public Student getStudentByIdForHibernate(Integer id) {
return hibernateTemplate.get(Student.class, id);
}
"unchecked", "deprecation" }) ({
public Student getStudentByNameForHibernate(String studentName) {
// Example 查询
Student example = new Student();
example.setStudentName(studentName);
List<Student> students = hibernateTemplate.findByExample(example);
// Hql 查询
students = (List<Student>) hibernateTemplate.find("from Student where studentName like ?0",
String.format("%%%s%%", studentName));
return students.isEmpty() ? null : students.get(0);
}
/**
* criteria 多条件查询,示例 sql
* select * from test_student where ( studentName like ? and ( id between ? and ? or this_.card_id is null))
*/
"unchecked") (
public List<Student> getStudentsByNameForHibernate(String studentName) {
DetachedCriteria criteria = DetachedCriteria.forClass(Student.class);
criteria.add(
Restrictions.and(
Restrictions.like("studentName", studentName, MatchMode.ANYWHERE),
Restrictions.or(
Restrictions.between("id", 1, 10),
Restrictions.isNull("studentCard")
)
)
);
return (List<Student>) hibernateTemplate.findByCriteria(criteria);
}
/**
* 127.0.0.1/api/hibernate/student/9 ---- get
*/
"/hibernate/student/{id}") (
public Student getStudentByIdForHibernate( Integer id) {
return studentService.getStudentByIdForHibernate(id);
}
/**
* 127.0.0.1/api/hibernate/student?studentName=HymanHu ---- get
*/
"/hibernate/student") (
public Student getStudentByNameForHibernate( String studentName) {
return studentService.getStudentByNameForHibernate(studentName);
}
/**
* 127.0.0.1/api/hibernate/students?studentName=HymanHu ---- get
*/
"/hibernate/students") (
public List<Student> getStudentsForHibernate( (required = false) String studentName) {
return studentService.getStudentsByNameForHibernate(studentName);
}
<!-- Spring data jpa -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
<!-- 加载属性文件 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:config/properties/jdbcParms.properties</value>
<value>classpath:config/properties/hibernateParms.properties</value>
</list>
</property>
</bean>
<!-- 引入别的配置文件 -->
<import resource="springJpa.xml"/>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
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
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd" >
<!-- 数据源,spring jdbc || c3p0 jdbc -->
<!-- <bean id="dataSourceForJpa" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean> -->
<!-- 配置 c3p0 数据源 -->
<bean id="dataSourceForJpa" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClassName}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 连接池中连接用完时,c3p0 一次性创建的连接数 -->
<property name="acquireIncrement" value="${pool.acquireIncrement}" />
<!-- 初始化连接数,在 minPoolSize 和 maxPoolSize 之间 -->
<property name="initialPoolSize" value="${pool.initialPoolSize}" />
<property name="minPoolSize" value="${pool.minPoolSize}" />
<property name="maxPoolSize" value="${pool.maxPoolSize}" />
<!-- 连接关闭时默认将所有未提交的操作回滚,默认为 false -->
<property name="autoCommitOnClose" value="${pool.autoCommitOnClose}"/>
</bean>
<!-- 配置 entityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!-- 设置数据源 -->
<property name="dataSource" ref="dataSourceForJpa" />
<!-- 扫描 Entity 包 -->
<property name="packagesToScan" value="com.sfac.springMvc.module.*.entity" />
<!-- 指定 JPA 实现厂商 -->
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
</property>
<!-- 指定 JPA 供应商适配器 -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<!-- 配置 JPA 属性 -->
<property name="jpaProperties">
<props>
<!-- 动态生成表策略 -->
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
<!-- 命名规则隐式策略 -->
<prop key="hibernate.implicit_naming_strategy">${hibernate.implicit_naming_strategy}</prop>
<!-- 数据库方言 -->
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
</props>
</property>
</bean>
<!-- 配置事务管理器 -->
<bean id="jpaTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="jpaTransactionManager"/>
<!-- 配置 repository -->
<jpa:repositories base-package="com.sfac.springMvc.module.*.repository"
transaction-manager-ref="jpaTransactionManager"
entity-manager-factory-ref="entityManagerFactory" />
</beans>
public interface StudentRepository extends JpaRepository<Student, Integer> {
}
private StudentRepository studentRepository;
"jpaTransactionManager") (
public ResultEntity<Student> insertStudentForJpa(Student student) {
studentRepository.saveAndFlush(student);
return new ResultEntity<Student>(ResultStatus.SUCCESS.status, "Insert success.", student);
}
/**
* 127.0.0.1/api/jpa/student ---- post
* {"studentName":"HymanHu"}
* {"studentName":"HymanHu", "studentCard":{"cardNo":"studentCard001"}}
*/
value = "/jpa/student", consumes = "application/json") (
public ResultEntity<Student> insertStudentForJpa( Student student) {
return studentService.insertStudentForJpa(student);
}
"jpaTransactionManager") (
public ResultEntity<Student> updateStudentForJpa(Student student) {
studentRepository.saveAndFlush(student);
// int i = 1 / 0;
return new ResultEntity<Student>(ResultStatus.SUCCESS.status, "Update success.", student);
}
/**
* 127.0.0.1/api/jpa/student ---- put
* {"id":"2","studentName":"HymanHu1"}
* {"id":"2","studentName":"HymanHu1","studentCard":{"id":"1","cardNo":"studentCard002"}}
*/
value = "/jpa/student", consumes = "application/json") (
public ResultEntity<Student> updateStudentForJpa( Student student) {
return studentService.updateStudentForJpa(student);
}
public ResultEntity<Object> deleteStudentForJpa(Integer id) {
studentRepository.deleteById(id);
return new ResultEntity<Object>(ResultStatus.SUCCESS.status, "Delete success.");
}
/**
* 127.0.0.1/api/jpa/student/2 ---- delete
*/
"/jpa/student/{id}") (
public ResultEntity<Object> deleteStudentForJpa( Integer id) {
return studentService.deleteStudentForJpa(id);
}
public Student getStudentByIdForJpa(Integer id) {
return studentRepository.findById(id).orElse(null);
}
public List<Student> getStudentsForJpa() {
return studentRepository.findAll();
}
/**
* 127.0.0.1/api/jpa/student/9 ---- get
*/
"/jpa/student/{id}") (
public Student getStudentByIdForJpa( Integer id) {
return studentService.getStudentByIdForJpa(id);
}
/**
* 127.0.0.1/api/jpa/students ---- get
*/
"/jpa/students") (
public List<Student> getStudentsForJpa( (required = false) String studentName) {
return studentService.getStudentsForJpa();
}
public interface StudentRepository extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {
}
public Page<Student> getStudentsBySearchBeanForJpa(SearchBean searchBean) {
searchBean.initSearchBean();
// Page<Student> page = exampleAndPage(searchBean);
Page<Student> page = criteriaAndPage(searchBean);
return page;
}
/**
* -实现方式一:Example + Page 查询
*/
public Page<Student> exampleAndPage(SearchBean searchBean) {
// 创建 Pageable 对象
String orderBy = StringUtils.isBlank(searchBean.getOrderBy()) ? "id" : searchBean.getOrderBy();
Sort.Direction direction = StringUtils.isBlank(searchBean.getDirection()) ||
searchBean.getDirection().equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC;
Sort sort = new Sort(direction, orderBy);
// 当前页起始为 0
Pageable pageable = PageRequest.of(searchBean.getCurrentPage() - 1, searchBean.getPageSize(), sort);
// 创建 Example 对象
Student student = new Student();
student.setStudentName(searchBean.getKeyWord());
student.setCreateDate(LocalDateTime.of(2021, 1, 19, 0, 0));
// matchingAny 相当于 or 连接查询条件,matching 相当于 and 连接查询条件
ExampleMatcher exampleMatcher = ExampleMatcher.matchingAny()
// 模糊查询,即 %{studentName} %
.withMatcher("studentName", match -> match.contains())
// 时间类型不支持模糊查询,生成的语句为createDate=?,同时也不支持 id > startId && id < endId 这样的操作
.withMatcher("createDate", match -> match.contains())
// 忽略基本数据类型字段,如果使用包装类则无需忽略
.withIgnorePaths("id");
Example<Student> example = Example.of(student, exampleMatcher);
return studentRepository.findAll(example, pageable);
}
/**
* -实现方式:Criteria + Page
*/
public Page<Student> criteriaAndPage(SearchBean searchBean) {
// 创建 Pageable 对象
String orderBy = StringUtils.isBlank(searchBean.getOrderBy()) ? "id" : searchBean.getOrderBy();
Sort.Direction direction = StringUtils.isBlank(searchBean.getDirection()) ||
searchBean.getDirection().equalsIgnoreCase("asc") ? Sort.Direction.ASC : Sort.Direction.DESC;
Sort sort = new Sort(direction, orderBy);
// 当前页起始为 0
Pageable pageable = PageRequest.of(searchBean.getCurrentPage() - 1, searchBean.getPageSize(), sort);
// 创建 Specification 对象
Specification<Student> specification = new Specification<Student>() {
private static final long serialVersionUID = 1L;
/**
* -构造语句 select * from test_student where createDate>=? and
* (studentName like ? or id between 10 and 20) order by id desc limit ?
*/
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query,
CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.and(
criteriaBuilder.greaterThanOrEqualTo(
root.get("createDate").as(LocalDateTime.class),
LocalDateTime.of(2021, 1, 20, 0, 0)),
criteriaBuilder.or(
criteriaBuilder.like(root.get("studentName").as(String.class),
String.format("%%%s%%", searchBean.getKeyWord())),
criteriaBuilder.between(root.get("id"), 10, 20)
)
);
}
};
return studentRepository.findAll(specification, pageable);
}
/**
* 127.0.0.1/api/jpa/students ---- post
* {"currentPage":1, "pageSize":5, "keyWord":"hy", "orderBy":"id", "direction":"desc"}
* {"currentPage":1, "pageSize":5, "keyWord":"hu", "orderBy":"studentName", "direction":"asc"}
* -注意:排序字段不能使用下划线
*
*/
value = "/jpa/students", consumes = "application/json") (
public Page<Student> getStudentsBySearchBeanForJpa( SearchBean searchBean) {
return studentService.getStudentsBySearchBeanForJpa(searchBean);
}
List<Student> findByStudentName(String studentName);
public Student getStudentByNameForJpa(String studentName) {
List<Student> students = Optional.ofNullable(studentRepository.findByStudentName(studentName))
.orElse(Collections.emptyList());
return students.isEmpty() ? null : students.get(0);
}
/**
* 127.0.0.1/api/jpa/student?studentName=HymanHu ---- get
*/
"/jpa/student") (
public Student getStudentByNameForJpa( String studentName) {
return studentService.getStudentByNameForJpa(studentName);
}
//@Query(nativeQuery = true, value = "select * from test_student where id = :id")
value = "from Student where id = :id") (
Student getStudentById( ("id") Integer id);
//@Query(nativeQuery = true, value = "update test_student set student_name = :studentName where id = :id")
value = "update Student set studentName = :studentName where id = :id") (
void updateStudentName( ("studentName") String studentName, ("id") Integer id);
nativeQuery = true, value = "update test_student " + (
"set student_name = :#{#student.studentName} where id = :#{#student.id}")
void updateStudent( ("student") Student student);
public Student getStudentByIdV2ForJpa(Integer id) {
return studentRepository.getStudentById(id);
}
"jpaTransactionManager") (
public ResultEntity<Student> updateStudentNameForJpa(Student student) {
studentRepository.updateStudentName(student.getStudentName(), student.getId());
return new ResultEntity<Student>(ResultStatus.SUCCESS.status, "Update success.", student);
}
/**
* 127.0.0.1/api/jpa/student/2/v2 ---- get
*/
"/jpa/student/{id}/v2") (
public Student getStudentByIdV2ForJpa( Integer id) {
return studentService.getStudentByIdV2ForJpa(id);
}
/**
* 127.0.0.1/api/jpa/student/v2 ---- put
* {"id":"1","studentName":"HymanHu1"}
*/
value = "/jpa/student/v2",consumes = "application/json") (
public ResultEntity<Student> updateStudentNameForJpa( Student student) {
return studentService.updateStudentNameForJpa(student);
}
private EntityManager entityManager;
public ResultEntity<List<Student>> batchInsertStudentsForJpa(List<Student> students) {
// 方式一,调用父接口完成批量操作
// studentRepository.saveAll(students);
// studentRepository.flush();
// 方式二,引入 entityManager 进行批量操作,效率高于第一种方式
students.stream().forEach(item -> {
entityManager.persist(item);
});
entityManager.flush();
entityManager.clear();
return new ResultEntity<List<Student>>(ResultStatus.SUCCESS.status, "Update success.", students);
}
/**
* 127.0.0.1/api/jpa/students/v2 ---- post
* [{"studentName":"aa1"},{"studentName":"aa2"}]
*/
value = "/jpa/students/v2", consumes = "application/json") (
public ResultEntity<List<Student>> batchInsertStudentsForJpa( List<Student> students) {
return studentService.batchInsertStudentsForJpa(students);
}
<!-- Jsp 支持 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-impl</artifactId>
<version>1.2.5</version>
</dependency>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/templates/" p:suffix=".jsp"></bean>
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta charset="utf-8" />
<meta name="description" content="overview & stats" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<title>Java_Spring_Mvc</title>
<!-- css -->
<!-- bootstrap -->
<link href="/static/css/bootstrap.min.css" rel="stylesheet" />
<!-- fontawesome -->
<link href="/static/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet" />
<!-- text fonts -->
<link href="/static/css/fonts.googleapis.com.css" rel="stylesheet" />
<!-- ace styles -->
<link href="/static/css/ace.min.css" class="ace-main-stylesheet" id="main-ace-style" rel="stylesheet" />
<link href="/static/css/ace-skins.min.css" rel="stylesheet" />
<link href="/static/css/ace-rtl.min.css" rel="stylesheet" />
</head>
<body class="no-skin">
<!-- header -->
<%@ include file="../fragments/header.jsp"%>
<div class="main-container ace-save-state" id="main-container">
<!-- sidebar -->
<%@ include file="../fragments/sidebar.jsp"%>
<!-- main content -->
<div class="main-content">
<div class="main-content-inner">
<div class="breadcrumbs ace-save-state" id="breadcrumbs">
<ul class="breadcrumb">
<li><i class="ace-icon fa fa-home home-icon"></i><a href="#">Home</a></li>
<li class="active">Test</li>
</ul>
<div class="nav-search" id="nav-search">
<form class="form-search">
<span class="input-icon">
<input type="text" placeholder="Search ..." class="nav-search-input"
id="nav-search-input" autocomplete="off" />
<i class="ace-icon fa fa-search nav-search-icon"></i>
</span>
</form>
</div>
</div>
<div class="page-content">
<div class="page-header">
<h1> Test <small>
<i class="ace-icon fa fa-angle-double-right"></i> index page </small>
</h1>
</div>
<!-- /.page-header -->
<div class="row">
<div class="col-xs-12">
<div class="row">
<div class="space-6"></div>
<h1>This is jsp test index page.</h1>
<h3>获取变量</h3>
<p>userName: ${userName }</p>
<p>==================================================================</p>
<h3>数据格式化</h3>
<p>Date格式化: <fmt:formatDate value="${current1}" pattern="yyyy-MM-dd HH:mm:ss"/></p>
<p>LocalDateTime格式化:
<fmt:parseDate value="${current2}" pattern="y-M-dd'T'H:m:s" var="myParseDate"></fmt:parseDate>
<fmt:formatDate value="${myParseDate}" pattern="yyyy-MM-dd HH:mm:ss"></fmt:formatDate >
</p>
<p>数字式化百分比: <fmt:formatNumber type="percent" value="${number / 100 }" maxFractionDigits="3" />
<p>数字式化货币: <fmt:formatNumber type="currency" value="${number }" />
<p>数字式化: <fmt:formatNumber type="number" value="${number }" maxFractionDigits="2" />
<p>==================================================================</p>
<h3>判断标签</h3>
<c:set var="title" scope="page" value="青年"/>
<c:if test="${age > 10 && age < 20}">
<p>称谓: <c:out value="${title}"/><p>
</c:if>
<c:choose>
<c:when test="${age < 10}">
<p>称谓: 儿童<p>
</c:when>
<c:when test="${age >= 10 && age < 30}">
<p>称谓: 青年<p>
</c:when>
<c:when test="${age >= 30 && age < 50}">
<p>称谓: 中年<p>
</c:when>
<c:otherwise>
<p>称谓: 老年<p>
</c:otherwise>
</c:choose>
<p>==================================================================</p>
<h3>循环标签</h3>
<ul>
<c:forEach var="city" items="${cities }">
<li>${city.cityName }</li>
</c:forEach>
</ul>
<table>
<thead><tr><td>Id</td><td>CityName</td></tr></thead>
<tbody>
<c:forEach var="city" items="${cities }">
<tr><td>${city.cityId }</td><td>${city.cityName }</td></tr>
</c:forEach>
</tbody>
</table>
<select>
<c:forEach var="city" items="${cities }">
<option id="${city.cityId }">${city.cityName }</option>
</c:forEach>
</select>
<p>==================================================================</p>
<h3>Form</h3>
<form action="/test/city" method="post">
<input type="hidden" name="_method" value="put">
<input type="hidden" value="${city.cityId }" name="cityId" />
<p>City Name: <input type="text" name="cityName" value="${city.cityName }" /></p>
<p>Local City Name: <input type="text" name="localCityName" value="${city.localCityName }"/></p>
<p><button type="submit">Submit</button></p>
</form>
<p>==================================================================</p>
</div>
</div>
</div>
</div>
<!-- /.page-content -->
</div>
</div>
<!-- /.main-content -->
<!-- footer -->
<%@ include file="../fragments/footer.jsp"%>
</div>
<!-- /.main-container -->
<!-- js -->
<script src="/static/js/jquery-2.1.4.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script src="/static/js/ace-elements.min.js"></script>
<script src="/static/js/ace.min.js"></script>
<script src="/static/js/ace-extra.min.js"></script>
<script src="/static/js/custom.js"></script>
</body>
</html>
/**
* 127.0.0.1/test/index ---- get
*/
"/index") (
public String testIndexPage(ModelMap modelMap) {
List<City> cities = cityService.getCitiesByCountryId(522);
City city = cityService.getCityByCityId(2255);
modelMap.put("userName", "HymanHu");
modelMap.put("current1", new Date());
modelMap.put("current2", LocalDateTime.now());
modelMap.put("number", 23.453);
modelMap.put("age", 18);
modelMap.put("cities", cities.stream().limit(10).collect(Collectors.toList()));
modelMap.put("city", city);
return "test/index";
}
/**
* 127.0.0.1/test/city ---- put
*/
value = "/city", consumes = "application/x-www-form-urlencoded") (
public String updateCityForm( City city) {
cityService.updateCity(city);
return "redirect:/test/index";
}
public class OnlineUserCounterListener implements HttpSessionListener,
HttpSessionAttributeListener, ServletRequestListener {
private final static Logger LOGGER = LogManager.getLogger(OnlineUserCounterListener.class);
private static ThreadLocal<HttpServletRequest> httpServletRequestHolder = new ThreadLocal<HttpServletRequest>();
// Session 创建时,输出 Session id
public void sessionCreated(HttpSessionEvent se) {
LOGGER.debug(String.format("New session id: %s", se.getSession().getId()));
}
// Session 销毁时候,计数器减一
public void sessionDestroyed(HttpSessionEvent se) {
ServletContext servletContext = se.getSession().getServletContext();
Integer count = servletContext.getAttribute("onlineUserCounter") == null ?
0 : (Integer) servletContext.getAttribute("onlineUserCounter");
count --;
servletContext.setAttribute("onlineUserCounter", count);
}
// Session 添加 user,计数器加一,并输出用户登录信息
public void attributeAdded(HttpSessionBindingEvent se) {
ServletContext servletContext = se.getSession().getServletContext();
Integer count = servletContext.getAttribute("onlineUserCounter") == null ?
0 : (Integer) servletContext.getAttribute("onlineUserCounter");
if ("user".equals(se.getName())) {
count ++;
servletContext.setAttribute("onlineUserCounter", count);
User user = (User) se.getSession().getAttribute("user");
/*
* -因为 Listener 是由 Servlet 负责实例化,如果直接注入 RoleService,
* -拿不到 Spring 容器的 Bean,因此使用 WebApplicationContextUtils
*/
RoleService roleService =
WebApplicationContextUtils.getWebApplicationContext(servletContext).getBean(RoleService.class);
List<Role> roles = roleService.getRolesByUserId(user.getId());
HttpServletRequest httpServletRequest = httpServletRequestHolder.get();
LOGGER.debug(String.format("Online user count: %s", count));
LOGGER.debug(String.format("Login user name: %s", user.getUserName()));
LOGGER.debug(String.format("Login user roles: %s",
roles.stream().map(item -> item.getRoleName()).collect(Collectors.toList())));
LOGGER.debug(String.format("Login user ip: %s", httpServletRequest.getRemoteAddr()));
LOGGER.debug(String.format("Login date time: %s",
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now())));
}
}
// Session 移除 user,计数器减一
public void attributeRemoved(HttpSessionBindingEvent se) {
ServletContext servletContext = se.getSession().getServletContext();
Integer count = servletContext.getAttribute("onlineUserCounter") == null ?
0 : (Integer) servletContext.getAttribute("onlineUserCounter");
if ("user".equals(se.getName())) {
count --;
servletContext.setAttribute("onlineUserCounter", count);
}
}
public void attributeReplaced(HttpSessionBindingEvent se) {
}
public void requestInitialized(ServletRequestEvent sre) {
httpServletRequestHolder.set((HttpServletRequest)sre.getServletRequest());
}
public void requestDestroyed(ServletRequestEvent sre) {
httpServletRequestHolder.remove();
}
}
public class AuthFilter implements Filter {
private final static Logger LOGGER = LogManager.getLogger(AuthFilter.class);
private UserService userService;
/**
* Authorization: Basic userName:password
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse) response;
LOGGER.debug("RequestUri: " + req.getRequestURI());
String authHeader = req.getHeader("Authorization");
if (StringUtils.isNotBlank(authHeader) && authHeader.startsWith("Basic ")) {
String userNameAndPassword = authHeader.substring(authHeader.indexOf(" ") + 1);
String userName = userNameAndPassword.split(":")[0];
String password = userNameAndPassword.split(":")[1];
LOGGER.debug(String.format("Request header Auth:%s - %s", userName, password));
User user = userService.getUserByUserNameAndPassword(userName, password);
if (user != null) {
LOGGER.debug(String.format("User %s is auth success.", user.getUserName()));
req.getSession().setAttribute("user", user);
}
}
chain.doFilter(req, resp);
}
}
<filter>
<filter-name>authFilter</filter-name>
<filter-class>com.sfac.springMvc.filter.AuthFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>authFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>authFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>authFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
public class ControllerMonitorInterceptor extends HandlerInterceptorAdapter {
private final static Logger LOGGER = LogManager.getLogger(AuthFilter.class);
private static ThreadLocal<Long> threadLocal = new ThreadLocal<Long>();
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
LOGGER.debug("======== Pre Interceptor ========");
long startTime = System.currentTimeMillis();
threadLocal.set(startTime);
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
LOGGER.debug(String.format("Request uri: %s", request.getRequestURI()));
LOGGER.debug(String.format("Request method: %s", request.getMethod()));
LOGGER.debug(String.format("Request remote address: %s", request.getRemoteAddr()));
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
long endTime = System.currentTimeMillis();
long startTime = threadLocal.get();
LOGGER.debug(String.format("Elapse time: %s millisecond", (endTime - startTime)));
LOGGER.debug("======== After Interceptor ========");
}
}
<!-- 注册拦截器,可注册多个 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean id="controllerMonitorInterceptor" class="com.sfac.springMvc.interceptor.ControllerMonitorInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
name = "common_exception_log") (
public class ExceptionLog extends AbstractEntity {
private String ip;
private String path;
private String className;
private String methodName;
private String exceptionType;
private String exceptionMessage;
}
public class SpringMvcHandlerExceptionResolver implements HandlerExceptionResolver {
private final static Logger LOGGER = LogManager.getLogger(SpringMvcHandlerExceptionResolver.class);
private ExceptionLogService exceptionLogService;
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
ex.printStackTrace();
if (handler instanceof HandlerMethod) {
LOGGER.debug("======== Log exception into db ========");
String ip = IPUtils.getIpAddr(request);
HandlerMethod handlerMethod = (HandlerMethod) handler;
String className = handlerMethod.getBeanType().getName();
String methodName = handlerMethod.getMethod().getName();
String exceptionType = ex.getClass().getSimpleName();
String exceptionMessage = ex.getMessage();
ExceptionLog exceptionLog = new ExceptionLog();
exceptionLog.setIp(ip);
exceptionLog.setPath(request.getServletPath());
exceptionLog.setClassName(className);
exceptionLog.setMethodName(methodName);
exceptionLog.setExceptionType(exceptionType);
exceptionLog.setExceptionMessage(exceptionMessage);
exceptionLog.setCreateDate(LocalDateTime.now());
exceptionLogService.insertExceptionLog(exceptionLog);
if (isInterface(handlerMethod)) {
return jsonResult(ResultEntity.ResultStatus.FAILED.status, exceptionMessage);
} else {
return pageResult("common/500", exceptionMessage);
}
}
return null;
}
// 判断目标方法是否为接口
private boolean isInterface(HandlerMethod handlerMethod) {
Annotation[] classAnnotations = handlerMethod.getBeanType().getAnnotationsByType(RestController.class);
Annotation[] methodAnnotations = handlerMethod.getMethod().getAnnotationsByType(ResponseBody.class);
return (classAnnotations.length > 0) || (methodAnnotations.length > 0);
}
// 判断是否是 Ajax 请求
private boolean isAjax(HttpServletRequest request) {
return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"));
}
// 返回页面
private ModelAndView pageResult(String url, String message) {
Map<String, String> model = new HashMap<String, String>();
model.put("message", message);
// 直接返回页面
//return new ModelAndView(url, model);
// 重定向到错误页面控制器
return new ModelAndView("redirect:/common/500", model);
}
// 返回 Json 数据
private ModelAndView jsonResult(int status, String message) {
ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
mv.addObject("status", status);
mv.addObject("message", message);
return mv;
}
}
<!-- Spring Mvc 文件上传 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!-- 上传文件 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 1TB=1024GB,1GB=1024MB,1MB=1024KB,1KB=1024Byte,1Byte=8bit -->
<!-- 此处配置数值的单位是Byte,设置上传文件的最大尺寸为 10M:10*1024*1024 -->
<property name="maxUploadSize" value="10485760"></property>
<property name="defaultEncoding" value="UTF-8"></property>
</bean>
<p>==================================================================</p>
<h3>File Upload</h3>
<p>${message}</p>
<img src="${relativePath }" width="200px;" height="200px;">
<p>上传文件,使用multipart/form-data类型</p>
<form action="/test/file" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">上传</button>
</form>
<form action="/test/files" method="post" enctype="multipart/form-data">
<input type="file" name="files">
<input type="file" name="files">
<button type="submit">上传</button>
</form>
<p>==================================================================</p>
private ResourceConfigBean resourceConfigBean;
/**
* 127.0.0.1/test/file ---- post
*/
//@PostMapping(value = "/file", consumes = "multipart/form-data")
value = "/file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) (
public String uploadFile( MultipartFile file,
RedirectAttributes redirectAttributes) {
if (file.isEmpty()) {
redirectAttributes.addFlashAttribute("message", "Please select file");
return "redirect:/test/index";
}
String absolutePath = resourceConfigBean.getLocalPathForWindow() + file.getOriginalFilename();
String relativePath = resourceConfigBean.getResourcePath() + file.getOriginalFilename();
try {
File destFile = new File(absolutePath);
file.transferTo(destFile);
// 使用工具类 Files 来上传文件
// byte[] bytes = file.getBytes();
// Path path = Paths.get(absolutePath);
// Files.write(path, bytes);
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
redirectAttributes.addFlashAttribute("message", "Upload fail.");
return "redirect:/test/index";
}
redirectAttributes.addFlashAttribute("message", "Upload success.");
redirectAttributes.addFlashAttribute("relativePath", relativePath);
return "redirect:/test/index";
}
/**
* 127.0.0.1/test/files ---- post
*/
value="/files", consumes="multipart/form-data") (
public String uploadFiles( MultipartFile[] files,
RedirectAttributes redirectAttributes) {
boolean isEmpty = true;
try {
for (MultipartFile file : files) {
if (file.isEmpty()) {
continue;
}
String fileName = file.getOriginalFilename();
String destFilePath = resourceConfigBean.getLocalPathForWindow() + fileName;
File destFile = new File(destFilePath);
file.transferTo(destFile);
isEmpty = false;
}
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
redirectAttributes.addFlashAttribute("message", "Upload file failed.");
return "redirect:/test/index";
}
if (isEmpty) {
redirectAttributes.addFlashAttribute("message", "Please select file.");
} else {
redirectAttributes.addFlashAttribute("message", "Upload file success.");
}
return "redirect:/test/index";
}
<p>==================================================================</p>
<h3>File Download</h3>
<p><a href="/test/file3?fileName=11.jpg">download file</a></p>
<p>==================================================================</p>
/**
* 127.0.0.1/test/file1?fileName=***.jpg ---- get
*/
"/file1") (
public void downloadFile1(HttpServletRequest request,
HttpServletResponse response, String fileName) {
String filePath = resourceConfigBean.getLocalPathForWindow() + fileName;
File downloadFile = new File(filePath);
if (downloadFile.exists()) {
response.setContentType("application/octet-stream");
response.setContentLength((int)downloadFile.length());
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
String.format("attachment; filename=\"%s\"", fileName));
byte[] buffer = new byte[1024];
FileInputStream fis = null;
BufferedInputStream bis = null;
try {
fis = new FileInputStream(downloadFile);
bis = new BufferedInputStream(fis);
OutputStream os = response.getOutputStream();
int i = 0;
while ((i = bis.read(buffer)) != -1) {
os.write(buffer, 0, i);
}
} catch (Exception e) {
LOGGER.debug(e.getMessage());
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
}
if (bis != null) {
bis.close();
}
} catch (Exception e2) {
LOGGER.debug(e2.getMessage());
e2.printStackTrace();
}
}
}
}
/**
* 127.0.0.1/test/file2?fileName=***.jpg ---- get
*/
"/file2") (
public void downloadFile2(HttpServletRequest request,
HttpServletResponse response, String fileName) {
String filePath = resourceConfigBean.getLocalPathForWindow() + fileName;
File downloadFile = new File(filePath);
try {
if (downloadFile.exists()) {
response.setContentType("application/octet-stream");
response.setContentLength((int)downloadFile.length());
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
String.format("attachment; filename=\"%s\"", fileName));
InputStream is = new FileInputStream(downloadFile);
IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
}
} catch (Exception e) {
LOGGER.debug(e.getMessage());
e.printStackTrace();
}
}
/**
* 127.0.0.1/test/file3?fileName=***.jpg ---- get
*/
"/file3") (
public ResponseEntity<Resource> downLoadFile( String fileName) {
try {
Resource resource = new UrlResource(Paths.get(
resourceConfigBean.getLocalPathForWindow() + fileName).toUri());
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, "application/octet-stream")
.header(HttpHeaders.CONTENT_DISPOSITION,
String.format("attachment; filename=\"%s\"", resource.getFilename()))
.body(resource);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
<!-- 国际化配置 -->
<!--
设置国际化解析器,将 Locale 对象存储于 Session 中供后续使用
localeResolver 名字不能变更
-->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
<property name="defaultLocale" value="zh_CN" />
</bean>
<!--
messageSource:配置国际化资源文件的路径
basenames | basename:classpath:i18n/language 下国际化文件
文件格式:***_语言_国家.properties:language_zh_CN.properties、language_en_US.properties
useCodeAsDefaultMessage:true,当 Spring 在 ResourceBundle 中找不到 messageKey 的话,
不抛出 NoSuchMessageException,而是返回 messageKey
-->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="defaultEncoding" value="UTF-8" />
<property name="useCodeAsDefaultMessage" value="true" />
<!-- <property name="basename" value="classpath:i18n/language" /> -->
<property name="basenames">
<list>
<value>classpath:i18n/language</value>
</list>
</property>
</bean>
<!-- 该拦截器通过请求参数 lang 来拦截 Http 请求,使其重新设置页面的区域化信息 -->
<mvc:interceptors>
<bean id="localeChangeInterceptor"
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="lang" />
</bean>
</mvc:interceptors>
<% taglib uri="http://www.springframework.org/tags" prefix="spring"%>
当前语言: ${pageContext.response.locale }
<input
type="text"
class="form-control"
name="userName"
placeholder=<spring:message code="userName" />
/>
<span class="bigger-110"><spring:message code="login" /></span>
/**
* 127.0.0.1/i18nLogin?lang=zh_CN
* 127.0.0.1/i18nLogin?lang=en_US
*/
"/i18nLogin") (
public String i18nLoginPage() {
return "account/i18nLogin";
}
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.1</version>
</plugin>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-typehandlers-jsr310</artifactId>
<version>1.0.2</version>
</dependency>
public class WebConfig extends WebMvcConfigurerAdapter{
public MappingJackson2HttpMessageConverter jacksonMessageConverter(){
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
//Registering Hibernate4Module to support lazy objects
Hibernate4Module hm = new Hibernate4Module();
//Force Jackson serialize lazy objects
hm.configure(Hibernate4Module.Feature.FORCE_LAZY_LOADING,true);
mapper.registerModule(hm);
messageConverter.setObjectMapper(mapper);
return messageConverter;
}
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//Here we add our custom-configured HttpMessageConverter
converters.add(jacksonMessageConverter());
super.configureMessageConverters(converters);
}
}