spring-boot-starter-parent
Spring Boot的父级依赖,表示当前的项目就是Spring Boot项目spring-boot-starter-web
导入web场景的所有依赖druid-spring-boot-starter
具有Druid支持的Spring Boot,可帮助您简化Spring Boot中的Druid配置spring-boot-starter-jdbc
通过HikariCP连接池使用JDBC的入门mysql-connector-java
用于MySQL的JDBC 驱动程序mybatis-spring-boot-starter
SpringBoot 整合 MyBatisspring-boot-starter-test
导入junit 测试的所有依赖mapper-spring-boot-starter
通用Mapper启动器package cn.lemon;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("cn.lemon.dao")/*@MapperScan("dao所在的包"),自动搜索包中的接口,产生dao的代理对象*/
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#修改端口为 ;1000,系统默认端口为:8080
server.port=1000
#如果想在控制台打印日志是需要配置的,因为我们记录的log级别是debug,默认是显示info以上
#SpringBoot通过`logging.level.*=debug`来配置日志级别,*填写包名
logging.level.cn.lemon=debug
#数据库连接配置
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/springboot
jdbc.username=root
jdbc.password=lemon
# mybatis 别名扫描
mybatis.type-aliases-package=cn.lemon.domain
# mapper.xml文件位置,如果没有映射文件,请注释掉
mybatis.mapper-locations=classpath:mappers/*.xml
package cn.lemon.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class JdbcConfig {
@Bean
@ConfigurationProperties(prefix = "jdbc")
public DataSource dataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
}
package cn.lemon.domain;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
@Table(name = "tb_user")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")/*可以省略*/
private Integer id;
@Column(name = "user_name")
private String userName;
private String password;
private String name;
private Integer age;
private Integer sex;
private Date birthday;
private Date created;
private Date updated;
private String note;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getUpdated() {
return updated;
}
public void setUpdated(Date updated) {
this.updated = updated;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
}
package cn.lemon.dao;
import cn.lemon.domain.User;
import tk.mybatis.mapper.common.Mapper;
public interface UserDao extends Mapper<User> {
}
package cn.lemon.dao;
import cn.lemon.domain.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserDaoTest {
@Autowired
private UserDao userDao;
@Test
public void testFindAll() {
List<User> userList = userDao.selectAll();
for (User user : userList) {
System.out.println(user.getId() + "\t" + user.getUserName() + "\t" + user.getPassword());
}
}
}
首先我们定义一个拦截器:
package cn.lemon.interceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginInterceptor implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.debug("处理器执行之前执行");
return true;/*若返回 false,处理器不会跳转*/
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.debug("处理器执行之后执行");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
logger.debug("完成页面跳转后执行");
}
}
然后定义配置类,注册拦截器:
package cn.lemon.config;
import cn.lemon.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
/**
* 通过@Bean注解,将我们定义的拦截器注册到Spring容器
*/
@Bean
public LoginInterceptor loginInterceptor() {
return new LoginInterceptor();
}
/**
* 重写接口中的addInterceptors方法,添加自定义拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 通过registry来注册拦截器,通过addPathPatterns来添加拦截路径
registry.addInterceptor(this.loginInterceptor()).addPathPatterns("/**");
}
}
package cn.lemon.controller;
import cn.lemon.dao.UserDao;
import cn.lemon.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@Controller
public class UserController {
@Autowired
private UserDao userDao;
@GetMapping("/all")
public String findUserAll(Model model) {
List<User> userList = userDao.selectAll();//查询所有用户
model.addAttribute("users", userList);
return "users";
}
}
spring-boot-starter-thymeleaf
SpringBoot会自动为Thymeleaf注册一个视图解析器,与解析JSP的InternalViewResolver类似,Thymeleaf也会根据前缀和后缀来确定模板文件的位置:
classpath:/templates/
.html
所以如果我们返回视图:users
,会指向到 classpath:/templates/users.html
xmlns:th="http://www.thymeleaf.org"
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页title>
<style type="text/css">
table {border-collapse: collapse; font-size: 14px; width: 80%; margin: auto}
table, th, td {border: 1px solid darkslategray;padding: 10px}
style>
head>
<body>
<div style="text-align: center">
<span style="color: darkslategray; font-size: 30px">欢迎光临!span>
<hr/>
<table class="list">
<tr>
<th>编号th>
<th>姓名th>
<th>用户名th>
<th>年龄th>
<th>性别th>
<th>生日th>
<th>备注th>
tr>
<tr th:each="user : ${users}">
<td th:text="${user.id}">1td>
<td th:text="${user.name}">张三td>
<td th:text="${user.userName}">zhangsantd>
<td th:text="${user.age}">20td>
<td th:text="${user.sex} == 1 ? '男': '女'">男td>
<td th:text="${#dates.format(user.birthday, 'yyyy-MM-dd')}">1980-02-30td>
<td th:text="${user.note}">1td>
tr>
table>
div>
body>
html>
Thymeleaf会在第一次对模板解析之后进行缓存,极大的提高了并发处理能力。但是这给我们开发带来了不便,修改页面后并不会立刻看到效果,我们开发阶段可以关掉缓存使用:
#开发阶段关闭thymeleaf的模板缓存
spring.thymeleaf.cache=false
注意:
Ctrl + Shift + F9
对项目进行rebuild才可以。${session.user.name}
<span th:text="${book.author.name}">
<li th:each="book : ${books}">
*{customer.name}
<div th:object="${book}">
...
<span th:text="*{title}">...span>
...
div>
@{/order/list}
@{/order/details(id=${orderId})}
@{../documents/report}
让我们看这些表达式:
<form th:action="@{/createOrder}">
<a href="main.html" th:href="@{/main}">
<div th:object="${session.user}">
<p>Name: <span th:text="*{firstName}">Sebastianspan>.p>
<p>Surname: <span th:text="*{lastName}">Pepperspan>.p>
<p>Nationality: <span th:text="*{nationality}">Saturnspan>.p>
div>
这是完全等价于:
<div th:object="${session.user}">
<p>Name: <span th:text="${session.user.firstName}">Sebastianspan>.p>
<p>Surname: <span th:text="${session.user.lastName}">Pepperspan>.p>
<p>Nationality: <span th:text="${session.user.nationality}">Saturnspan>.p>
div>
当然,美元符号和星号语法可以混合使用:
<div th:object="${session.user}">
<p>Name: <span th:text="*{firstName}">Sebastianspan>.p>
<p>Surname: <span th:text="${session.user.lastName}">Pepperspan>.p>
<p>Nationality: <span th:text="*{nationality}">Saturnspan>.p>
div>
'one text', 'Another one!',…
0, 34, 3.0, 12.3,…
true, false
null
one, sometext, main,…
+
|The name is ${name}|
+, -, *, /, %
-
and, or
!, not
>, <, >=, <= (gt, lt, ge, le)
==, != (eq, ne)
(if) ? (then)
(if) ? (then) : (else)
(defaultvalue)
所有这些特征可以被组合并嵌套:
'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))
还有非常多的标签,这里只列出最常用的几个,由于一个标签内可以包含多个th:x属性,其生效的优先级顺序为:include,each,if/unless/switch/case,with,attr/attrprepend/attrappend,value/href,src ,etc,text/utext,fragment,remove
<p th:text="${collect.description}">descriptionp>
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
字符串拼接还有另外一种简洁的写法:
<span th:text="|Welcome to our application, ${user.name}!|">
Thymeleaf中使用th:if和th:unless属性进行条件判断,下面的例子中,标签只有在
th:if
中条件成立时才显示:
<a th:if="${myself=='yes'}" > i> a>
Logina>
th:unless于th:if恰好相反,只有表达式中的条件不成立,才会显示其内容。
也可以使用 (if) ? (then) : (else)
这种语法来判断显示的内容
<tr th:each="collect,iterStat : ${collects}">
<th scope="row" th:text="${collect.id}">1th>
<td >
<img th:src="${collect.webLogo}"/>
td>
<td th:text="${collect.url}">Marktd>
<td th:text="${collect.title}">Ottotd>
<td th:text="${collect.description}">@mdotd>
<td th:text="${terStat.index}">indextd>
tr>
iterStat
称作状态变量,属性有:
URL在Web应用模板中占据着十分重要的地位,需要特别注意的是Thymeleaf对于URL的处理是通过语法@{…}
来处理的。 如果需要Thymeleaf对URL进行渲染,那么务必使用th:href
,th:src
等属性,下面是一个例子:
<a th:href="@{/standard/{type}(type=${type})}">viewa>
<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">viewa>
设置背景
<div th:style="'background:url(' + @{/} + ');' ">div>
根据属性值改变背景:
<div class="media-object resource-card-image" th:style="'background:url(' + @{(${collect.webLogo}=='' ? 'img/favicon.png' : ${collect.webLogo})} + ')'" >div>
说明:
(orderId=${o.id})
表示将括号内的内容作为URL参数处理,该语法避免使用字符串拼接,大大提高了可读性@{...}
表达式中可以通过{orderId}
访问Context中的orderId变量@{/order}
是Context相关的相对路径,在渲染时会自动添加上当前Web应用的Context名字,假设context名字为app,那么结果应该是/app/order内联文本:[[…]]
内联文本的表示方式,使用时,必须先用th:inline=”text/javascript/none”
激活,th:inline
可以在父级标签内使用,甚至作为body的标签。内联文本尽管比th:text
的代码少,不利于原型显示。
js附加代码:
/*[+
var msg = 'This is a working application';
+]*/
js移除代码:
/*[- */
var msg = 'This is a non-working template';
/* -]*/
为了模板更加易用,Thymeleaf还提供了一系列Utility对象(内置于Context中),可以通过#
直接访问:
#dates
,面向java.util.Calendar下面用一段代码来举例一些常用的方法:
dates
/*
* Format date with the specified pattern
* Also works with arrays, lists or sets
*/
${#dates.format(date, 'dd/MMM/yyyy HH:mm')}
${#dates.arrayFormat(datesArray, 'dd/MMM/yyyy HH:mm')}
${#dates.listFormat(datesList, 'dd/MMM/yyyy HH:mm')}
${#dates.setFormat(datesSet, 'dd/MMM/yyyy HH:mm')}
/*
* Create a date (java.util.Date) object for the current date and time
*/
${#dates.createNow()}
/*
* Create a date (java.util.Date) object for the current date (time set to 00:00)
*/
${#dates.createToday()}
strings
/*
* Check whether a String is empty (or null). Performs a trim() operation before check
* Also works with arrays, lists or sets
*/
${#strings.isEmpty(name)}
${#strings.arrayIsEmpty(nameArr)}
${#strings.listIsEmpty(nameList)}
${#strings.setIsEmpty(nameSet)}
/*
* Check whether a String starts or ends with a fragment
* Also works with arrays, lists or sets
*/
${#strings.startsWith(name,'Don')} // also array*, list* and set*
${#strings.endsWith(name,endingFragment)} // also array*, list* and set*
/*
* Compute length
* Also works with arrays, lists or sets
*/
${#strings.length(str)}
/*
* Null-safe comparison and concatenation
*/
${#strings.equals(str)}
${#strings.equalsIgnoreCase(str)}
${#strings.concat(str)}
${#strings.concatReplaceNulls(str)}
/*
* Random
*/
${#strings.randomAlphanumeric(count)}
使用thymeleaf布局非常的方便
在/resources/templates/目录下创建footer.html,内容如下:
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div th:fragment="copy(title)">
© 2011 The Good Thymes Virtual Grocery
<span th:text="${title}">abcdefa234234span>
div>
body>
html>
在页面任何地方引入:
<body>
<div th:include="footer:: copy('pbj44')">div>
<div th:replace="footer:: copy('pbj44')">div>
body>
th:include
和 th:replace
区别,include只是加载,replace是替换
返回的HTML如下:
<body>
<div> © 2016 div>
<footer>© 2016 footer>
body>
下面是一个常用的后台页面布局,将整个页面分为头部,尾部、菜单栏、隐藏栏,点击菜单只改变content区域的页面
<body class="layout-fixed">
<div th:fragment="navbar" class="wrapper" role="navigation">
<div th:replace="fragments/header:: header">Headerdiv>
<div th:replace="fragments/left:: left">leftdiv>
<div th:replace="fragments/sidebar:: sidebar">sidebardiv>
<div layout:fragment="content" id="content" >div>
<div th:replace="fragments/footer:: footer">footerdiv>
div>
body>
pom.xml中加载测试启动器
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
测试类:
@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloControllerTest {
private MockMvc mvc;
@Before
public void setUp() throws Exception {
mvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();
}
@Test
public void getHello() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(equalTo("hello spring boot2")));
}
@Test
public void getHello2() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
}
mockmv常用的用法:
热启动在正常开发项目中已经很常见了吧,虽然平时开发web项目过程中,改动项目启重启总是报错;但springBoot对调试支持很好,修改之后可以实时生效,需要添加以下的配置:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<fork>truefork>
configuration>
plugin>
plugins>
build>
该模块在完整的打包环境下运行的时候会被禁用。如果你使用java -jar启动应用或者用一个特定的classloader启动,它会认为这是一个“生产环境”。
在application.yml文件加入spring:thymeleaf:cache: false 配置
如果你通过上面的步骤还没实现想要的热部署效果,可以继续做以下两个配置:
Ctrl + Shift +Alt + /
选择Registry,按照下图标注配置