目录
SpringBoot基础(二)
一、操作数据库
1. SpringBootJdbc
引入依赖 jdbc 和 mysql
SpringBoot默认支持的连接池策略,如果使用 jdbc 或者 jpa 就会自动连接连接池
- 优先寻找创建Tomcat连接池
- 如果没有Tomcat连接池,会查找创建HikariCP
- 如果没有HikariCP连接池,会查找创建dbcp
- 如果没有dbcp连接池,会查找创建dbcp2
- 可以使用spring.datasource.type属性指定连接池类型,比如其它的连接池 C3P0 或者 druid
application-jdbc.yml
#数据库jdbc连接url地址,serverTimezone设置数据库时区东八区 spring: datasource: url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8 username: xxx password: xxxxx driver-class-name: com.mysql.jdbc.Driver
UserDao 接口
public interface UserService { //获取全部用户数据 public List<User> getUserList(); //新增用户数据 public void createUser(User user); //获取指定id用户信息 public User getUser(Long id); //更新指定id用户信息 public void updateUser(Long id,User user); //删除指定id用户 public void deleteUser(Long id); }
UserDaoImpl
@Service public class UserServiceImpl implements UserService { //SpringBoot提供的数据库操作类 @Autowired JdbcTemplate jdbcTemplate; @Override public List<User> getUserList() { return jdbcTemplate.query("select * from users", new BeanPropertyRowMapper(User.class)); } @Override public void createUser(User user) { jdbcTemplate.update("insert into users(name,age)values(?,?)",user.getName(),user.getAge()); } @Override public User getUser(Long id) { final User user = new User(); /* jdbcTemplate.query("select * from users where id="+id,new RowCallbackHandler() { * @Override * public void processRow(ResultSet rs) throws SQLException { * user.setId(id); * user.setName(rs.getString("name")); * user.setAge(rs.getInt("age")); * } * }); */ return jdbcTemplate.queryForObject("select * from users where id=?",new BeanPropertyRowMapper(User.class),id); return user; } @Override public void updateUser(Long id, User user) { jdbcTemplate.update("update users set name=?,age=? where id=?",user.getName(),user.getAge(),id); } @Override public void deleteUser(Long id) { jdbcTemplate.update("delete from users where id=?",id); } }
2. SpringBoot 整合 Mybatis
引入依赖。mybatis,mysql,druid。
配置 application.yml
#数据库jdbc连接url地址,serverTimezone设置数据库时区东八区 spring: datasource: url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8 username: xxx password: xxxxx driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource #springboot整合mybatis mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.demo.domain
创建 实体bean、dao接口以及相应的 mapper 映射文件
3. SpringBott 使用JPA
1. JPA 介绍
- (Java Persistence(持久化) API):是注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
2. 优势
- 标准化:JPA 是 JCP 组织发布的 Java EE 标准之一,因此符合 JPA 标准的框架都提供相同的访问API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。JPA的实现产品有HIbernate,TopLink,OpenJPA等等。
- 容器级特性的支持:JPA框架中支持大数据集、事务、并发等容器级事务。
- JPA简单易用,集成方便:JPA的主要目标之一就是提供更加简单的编程模型。只需要使用 javax.persistence.Entity进行注释。JPA基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成。
- 查询能力:JPA的查询语言(JPQL)是面向对象而非面向数据库的。而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
- 高级特性:JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。
3. JPA 注解
注解 | 作用 |
---|---|
@Entity | 声明类为实体或表 |
@Table | 声明表名 |
@Basic | 指定非约束明确的各个字段 |
@Embedded | 指定类或它的值是一个可嵌入的类的实例的实体的属性 |
@Id | 指定的类的属性,用于识别(一个表中的主键) |
@GeneratedValue | 指定如何标识属性可以被初始化,例如自动、手动、或从序列表中获得的值 |
@Transient | 指定的属性,它是不持久的,即:该值永远不会存储在数据库中 |
@Column | 指定持久属性栏属性 |
@SequenceGenerator | 指定在@GeneratedValue注解中指定的属性的值。它创建了一个序列 |
@TableGenerator | 指定在@GeneratedValue批注指定属性的值发生器。它创造了的值生成的表 |
@AccessType | 这种类型的注释用于设置访问类型。如果设置@AccessType(FIELD),则可以直接访问变量并且不需要getter和setter,但必须为public。如果设置@AccessType(PROPERTY),通过getter和setter方法访问Entity的变量 |
@JoinColumn | 指定一个实体组织或实体的集合。这是用在多对一和一对多关联 |
@UniqueConstraint | 指定的字段和用于主要或辅助表的唯一约束 |
@ColumnResult | 参考使用select子句的SQL查询中的列名 |
@ManyToMany | 定义了连接表之间的多对多一对多的关系 |
@ManyToOne | 定义了连接表之间的多对一的关系 |
@OneToMany | 定义了连接表之间存在一个一对多的关系 |
@OneToOne | 定义了连接表之间有一个一对一的关系 |
@NamedQueries | 指定命名查询的列表 |
@NamedQuery | 指定使用静态名称的查询 |
4. JPA入门
加入依赖jpa、mysql、druid(非必须,自带有连接池)、lombok(非必须)。
配置 application.yml
spring: datasource: url : jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8 type : com.alibaba.druid.pool.DruidDataSource username : xxx password : xxxx driver-class-name : com.mysql.cj.jdbc.Driver jpa: hibernate: # create:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表。 # create-drop:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。 # validate:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。 # update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。 ddl-auto: update show-sql: true
实体类
@Data @AllArgsConstructor @NoArgsConstructor @Entity public class Person { @Id @GeneratedValue private Long id; @Column(name = "name", nullable = true, length = 20) private String name; @Column(name = "age", nullable = true, length = 4) private int age; @Column(name = "password", nullable = true, length = 20) private String password; @OneToMany(mappedBy = "personId", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Dog> dogs; }
@Entity @Data @NoArgsConstructor @AllArgsConstructor public class Dog { @Id @GeneratedValue private Long id; private String name; private Long personId; }
5.自定义查询
- 我们dao 需要继承 JpaRepository<Person, Long>,Person 为实体类,Long为主键类型。这个类的内部已经定义好了单表数据操作语句,可以直接使用。但是有的时候,根据业务需求需要自定义一些数据操作,这就需要了解一下方法命名规则,根据方法名会自动生成相应的JPQL。
关键词 | 样例 | SQL符号 | 对应JPQL 语句片段 |
---|---|---|---|
And | findByLastnameAndFirstname | and | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | or | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstname,findByFirstnameIs,findByFirstnameEquals | = | … where x.firstname = ?1 |
Between | findByStartDateBetween | between xxx and xxx | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | < | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | <= | … where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | > | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | >= | … where x.age >= ?1 |
After | findByStartDateAfter | > | … where x.startDate > ?1 |
Before | findByStartDateBefore | < | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | is null | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | is not null | … where x.age not null |
Like | findByFirstnameLike | like | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | not like | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | like ‘xxx%’ | … where x.firstname like ?1(parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | like ‘xxx%’ | … where x.firstname like ?1(parameter bound with prepended %) |
Containing | findByFirstnameContaining | like ‘%xxx%’ | … where x.firstname like ?1(parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | order by | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | <> | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | in() | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection ages) | not in() | … where x.age not in ?1 |
TRUE | findByActiveTrue() | =true | … where x.active = true |
FALSE | findByActiveFalse() | =false | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | upper(xxx)=upper(yyyy) | … where UPPER(x.firstame) = UPPER(?1) |
6. 数据访问层接口
除了使用按照规则定义方法为,还可以使用 JPQL。
查询使用
@Query
,如果使用原生sql语句,需要使用@Query(value="原生sql",nativeQuery=true)
。对表更新和删除需要使用
@Modifying、@Transactional、@Query
的注解组合。对于
@Transactional
来说,readOnly=true
表明所注解的方法或类只是读取数据。默认值就是这个。readOnly=false
表明所注解的方法或类是增加,删除,修改数据。- 事物隔离级别
- 隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
- TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:这是事务最低的隔离级别。它允许令外一个事务可以看到这个事务未提交的数据,这种隔离级别会产生脏读,不可重复读和幻像读。
- TransactionDefinition.ISOLATION_READ_COMMITTED:保证一个事务修改的数据提交后才能被另外一个事务读取。避免脏读。但是不可重复读和幻读有可能发生
- TransactionDefinition.ISOLATION_REPEATABLE_READ:避免脏读和不可重复读,但是幻读有可能发生。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
- 事务传播行为
- 事务传播行为指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。TransactionDefinition定义了七种传播行为:
- TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。
在进行分页的时候,在方法最后加上一个参数 Pageable。
// JpaRepository 中已经定义有关于单表的操作方法
public interface PersonRepository extends JpaRepository<Person, Long> {
//查询指定用户姓名的用户
public Person findByName(String name);
//查询指定用户姓名的用户
public Person findByNameAndPassword(String name, String password);
//查询包含指定名字的用户
public List<Person> findByNameContaining(String name);
// 排序查询,返回list集合。Sort 在controller层中使用 new Sort(Sort.Direction.fromString("desc"),"id")
public List<Person> findByNameContaining(String name, Sort sort);
//分页查询, 查询计算元素总个数、总页数,数据多的情况下,代价是昂贵的。Pageable 在controller层中使用PageRequest.of(page, size,new Sort(Sort.Direction.fromString("desc"),"id"))
public Page<Person> findByNameContaining(String name , Pageable pageable);
//分页查询,返回的是一个片段,它只知道下一片段或者上一片段是否可用。
public Slice<Person> getByNameContaining(String name, Pageable pageable);
//查询指定用户姓名的用户。使用 :name 作为占位符,需要使用 @Param 注解
@Query("select p from Person p where p.name = :name")
public Person getPerson(@Param("name") String name);
//用户登录验证。使用方法中的参数位置作为占位符
@Query("select p from Person p where p.name = ?1 and p.password= ?2")
public Person login(String name, String password);
//模糊查询用户名里面包含指定字符
@Query("select p from Person p where p.name like %:name%")
public List<Person> getNamesLike(@Param("name") String name);
//查询密码位数是5位数的全部用户,使用mysql原始sql语句进行查询
@Query(value="select * from person where length(password)=5",nativeQuery=true)
public List<Person> getPasswordisFive();
@Modifying
@Transactional(readOnly = true)
@Query("update Person p set p.name=?2 where p.id=?1")
public int UpdateName(Long id, String name);
@Modifying
@Transactional
@Query("delete from Person p where p.name=?1")
public int DeleteName(String name);
//查询指定用户名称,按照id降序排序第一条记录
Person findFirstByNameOrderByIdDesc(String name);
//模糊查询指定用户名称,按照id降序排序前10条记录
public List<Person> findFirst10ByNameLikeOrderByIdDesc(String name);
//查询指定用户名称,按照id升序排序第一条记录
Person findTopByNameOrderByIdAsc(String name);
//模糊查询指定用户名称,按照id升序排序前10条记录
public List<Person> findTop10ByNameLikeOrderByIdAsc(String name);
@Query("select p from Person p join p.dogs d where p.id = ?1")
public Person findPerson(Long id);
}
二、使用 Thymeleaf 模版引擎
介绍:Thymeleaf是xml/xhtml/html5的模板引擎。Thymeleaf 开箱即用的特性。它提供标准和spring标准两种方言,可以直接套用模板实现JSTL、 OGNL表达式效果,避免每天套模板、改jstl、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。Thymeleaf 提供spring标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。
集成 Thymeleaf
添加依赖 thymeleaf
修改配置 application.yml
spring: #开始thymeleaf设置 thymeleaf: #禁用模板缓存 cache: false
为模版注入数据
String message = "Hello, Thymeleaf!"; model.addAttribute("message", message); User u = new User(); u.setId(1L); u.setName("德鲁伊"); u.setAge(18); model.addAttribute("user", u); Map<String,Object> map=new HashMap<>(); map.put("src1","1.jpg"); map.put("src2","2.jpg"); model.addAttribute("src", map); List<User> list=new ArrayList<User>(); list.add(u1); list.add(u2); model.addAttribute("userList", list); model.addAttribute("href", "http://www.ujiuye.com"); model.addAttribute("flag", "yes"); model.addAttribute("menu", "admin"); model.addAttribute("manager", "manager"); //日期时间 Date date = new Date(); model.addAttribute("date", date); //小数的金额 double price=128.5678D; model.addAttribute("price", price); //定义大文本数据 String str="Thymeleaf是Web和独立环境的现代服务器端Java模板引擎,能够处理HTML,XML,JavaScript,CSS甚至纯文本。\r\n" + "Thymeleaf的主要目标是提供一种优雅和高度可维护的创建模板的方式。为了实现这一点,它建立在自然模板的概念上,将其逻辑注入到模板文件中,不会影响模板被用作设计原型。这改善了设计的沟通,弥补了设计和开发团队之间的差距。\r\n" + "Thymeleaf也从一开始就设计了Web标准 - 特别是HTML5 - 允许您创建完全验证的模板,如果这是您需要的\r\n" ; model.addAttribute("strText", str); //定义字符串 String str2="JAVA-offcn"; model.addAttribute("str2", str2);
在 resource/templates 下创建模版 index.html
<!doctype html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title th:text="#{title}"></title> </head> <body> <h1 th:text="${message}"></h1> <hr/> </br> <img th:src="${src.src1}"> </br> <img th:src="${src.src2}"/> </br> <span th:text="${user.id}"></span> <span th:text="${user.name}"></span> <span th:text="${user.age}"></span> <hr/> <table> <tr th:each="user,iterStat : ${userList}"> <td th:text="${user.id}"></td> <td th:text="${user.name}"></td> <td th:text="${user.age}"></td> <td th:text="${iterStat.index}">index</td> <td th:text="${iterStat.count}">index</td> </tr> </table> <hr/> <!-- 给标签赋值 th:text --> <h1 th:text="${userName}"></h1> <!-- 给属性赋值 th:value、th:属性名称 --> <input type="text" name="names" th:value="${userName}"/> </br> <em th:size="${userName}"></em> <!-- 字符串拼接 --> <span th:text="'欢迎来:'+${userName}+'学习!'"></span> </br> <!-- 字符串拼接,方式2 --> <a th:href="${href}"><span th:text="|欢迎来:${userName}学习!|"></span></a> <hr/> <!-- th:if 条件成立就显示 --> <h1 th:if="${flag=='yes'}" >中公教育</h1> <!-- th:unless 条件不成立就显示 --> <h1 th:unless="${flag=='no'}" >优就业</h1> <!-- switch选择语句 --> <div th:switch="${menu}"> <p th:case="'admin'">User is an administrator</p> <p th:case="${manager}">User is a manager</p> </div> <hr/> <!-- 把片段的内容插入到当前位置 --> <div th:insert="~{footer :: copy}"></div> </br> <!-- 使用片段的内容替换当前标签 --> <div th:replace="~{footer :: copy}"></div> </br> <!-- 保留自己的主标签,不要片段的主标签 (官方3.0后不推荐) --> <div th:include="~{footer :: copy}"></div> <hr/> 时间:<span th:text="${#dates.format(date,'yyyy-MM-dd HH:mm:ss')}">4564546</span></br> 金额:<span th:text="'¥'+${#numbers.formatDecimal(price, 1, 2)}">180</span> </br> <!-- # 这里的含义是 如果 atc.text 这个变量多余200个字符,后面显示... --> <p th:text="${#strings.abbreviate(strText,60)}">内容内容内容</p> <!-- 判断字符串是否为空 --> <span th:if="${!#strings.isEmpty(str2)}">字符串str2不为空</span></br> <!-- 截取字符串,指定长度 --> <span th:text="${#strings.substring(str2,0,4)}">字符串str2的值</span> <hr/> <form class="form-horizontal" th:action="@{/manageruser/edit}" th:object="${user}" method="post"> <input type="hidden" name="id" th:value="*{id}" /> <div class="form-group"> <label for="name" class="col-sm-2 control-label">name</label> <div class="col-sm-10"> <input type="text" class="form-control" name="name" id="name" th:value="*{name}" placeholder="name"/> </div> </div> <div class="form-group"> <label for="age" class="col-sm-2 control-label">age</label> <div class="col-sm-10"> <input type="text" class="form-control" name="age" id="age" th:value="*{age}" placeholder="age"/> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <input type="submit" value="Submit" class="btn btn-info" /> <a href="/manageruser/" th:href="@{/manageruser/}" class="btn btn-info">Back</a> </div> </div> </form> </body> </html>
<!-- footer.html--> <body> <h1 th:fragment="copy"> © 1999-2018 Offcn.All Rights Reserved </h1> </body>