相比于Mybatis,jpa的使用方便简洁,功能也很强大,也是springboot的集成模块里的!当然,Mybatis也有它的优点,比如灵活等,各有各的好!一个是看个人的喜欢,一个是看公司使用的技术栈,多掌握一门技术总是好的!
JPA、hibernate和spring-data-jpa的关系。
JPA(Java Persistence API)是将实体对象持久化到数据库中的一种规范。JPA仅仅是一种规范,也就是说JPA仅仅定义了一些接口,而接口是需要实现才能工作的。所以底层需要某种实现,而Hibernate就是实现了JPA接口的ORM框架。spirng data jpa是spring提供的一套简化JPA开发的框架,按照约定好的【方法命名规则】写dao层接口,就可以在不写接口实现的情况下,实现对数据库的访问和操作。同时提供了很多除了CRUD之外的功能,如分页、排序、复杂查询等等,底层还是使用了 Hibernate 的 JPA 技术实现。
一、添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
二、配置application.yml文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: yourUserName
password: yourPassword
url: jdbc:mysql://localhost:3306/jpa_test?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
jpa:
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5Dialect
# dialect: org.hibernate.dialect.MySQLDialect
format_sql: true
hibernate:
ddl-auto: update
# ddl-auto: create
说明:
1、使用第一个注释(dialect: org.hibernate.dialect.MySQLDialect),可能会在由实体类生成数据库表的时候报错(Error executing DDL),DDL是数据定义语言(Data Definition Language),用来创建数据库表和更新数据库表结构的。
2、使用第二个注释(ddl-auto: create),会把数据库中的所有表和数据都删除,然后再重新创建新表,所以要慎用!使用update就安全,也会创建表,更新表结构!为了安全起见,尽量避免使用ddl-auto: create。
3、serverTimezone=GMT%2B8,配置的是东八区。
4、format_sql: true,格式化显示sql,这个也很重要,方便查看sql的执行情况!
三、继承JpaRepository接口
/**
* @author river
* 2020/1/21
*/
public interface UserRepository extends JpaRepository<User,Integer> {
User findByUserName(String userName);
List<User> findByUserNameEndsWith(String userName);
List<User> findByUserNameStartsWith(String userName);
List<User> findByUserNameContains(String userName);
List<User> findByUserNameLike(String userName);
List<User> findByUserNameNotIn(String[] userNames);
@Override
Page<User> findAll(Pageable pageable);
/*原生查询,列名和表名使用数据库中的字段和表名*/
@Query(value = "select * from user where user_name like ?1",
countQuery = "select count(*) from user where user_name like ?1",nativeQuery = true)
Page<User> findByUserNameLike(String userName, Pageable pageable);
/*hql语句,列名和表名使用实体类的属性和类名,如果没有new User(),返回值List中是Object[],而不是User实体类*/
@Query(value = "select new User(u.userName, u.password) from User u where u.userName like :userName and u.createTime > :createTime")
List<User> findByUserNameLikeAndCreateTimeGreaterThan(@Param("userName")String userName, @Param("createTime") Date createTime);
}
说明:
1、JpaRepository<User,Integer>中,User是数据库对象的实体类,Integer是数据表的主键类型。
2、UserName为字段名,查询的时候,只要函数的命名符合jpa的规则,则不需要实现的方法体,我列举的几个常用的查询。
3、jpa实现分页的方式是使用Pageable接口。
4、可以使用原生查询和hql语句查询。其中hql中,需要注意的是new User(),描述在注释中写了。
四、编写测试类UserRepositoryTest
/**
* @author river
* 2020/1/21
*/
@Slf4j
@SpringBootTest
class UserRepositoryTest {
@Autowired
UserRepository userRepository;
@Test
void save() {
User user = new User("river","123456");
User user1 = userRepository.save(user);
log.info("id:{}, userName:{}, password:{}",user1.getId(),user1.getUserName(),user1.getPassword());
}
@Test
void findByUserName() {
User user = userRepository.findByUserName("river");
assertNotEquals(user,null);
log.info(user.getPassword());
}
@Test
void update(){
User user = userRepository.findByUserName("river");
user.setPassword("123123");
userRepository.save(user);
assertNotEquals(user,null);
log.info(user.getPassword());
}
@Test
void findByUserNameEndsWith() {
List<User> userList = userRepository.findByUserNameEndsWith("ver3");
assertEquals(userList.size(),1);
log.info(userList.get(0).getPassword());
}
@Test
void findByUserNameStartsWith() {
List<User> userList = userRepository.findByUserNameStartsWith("river");
assertNotEquals(userList.size(), 0);
}
@Test
void findByUserNameContains() {
List<User> userList = userRepository.findByUserNameContains("3");
assertEquals(userList.size(),1);
log.info(userList.get(0).getPassword());
}
@Test
void findByUserNameLike() {
List<User> userList = userRepository.findByUserNameLike("river%");
assertNotEquals(userList.size(), 0);
log.info("size:{}",userList.size());
userList = userRepository.findByUserNameLike("river_");
assertNotEquals(userList.size(), 0);
log.info("size:{}",userList.size());
}
@Test
void findByUserNameNotIn() {
String[] userNames = {"river", "river1", "river4"};
List<User> userList = userRepository.findByUserNameNotIn(userNames);
assertNotEquals(userList.size(), 0);
log.info("size:{}",userList.size());
}
@Test
void findAll() {
Sort sort = Sort.by(Sort.Direction.DESC,"createTime");//创建时间降序排序
PageRequest pageRequest = PageRequest.of(0,3,sort);//查询第一页,一页3条记录
Page<User> page = userRepository.findAll(pageRequest);
List<User> userList = page.getContent();
assertEquals(userList.size(), 3);
}
@Test
void findByUserName1() {
PageRequest pageRequest = PageRequest.of(0, 3);//查询第一页,一页3条记录
Page<User> page = userRepository.findByUserNameLike("river%",pageRequest);
List<User> userList = page.getContent();
assertEquals(userList.size(), 3);
}
@Test
void findByUserNameLikeAndCreateTimeGreaterThan() throws ParseException {
String dateString = "2020-01-13 21:53:57";
String strDateFormat = "yyyy-MM-dd HH:mm:ss";
DateFormat df = new SimpleDateFormat(strDateFormat);
Date date = df.parse(dateString);
List<User> userList = userRepository.findByUserNameLikeAndCreateTimeGreaterThan("river%", date);
assertNotEquals(userList.size(), 0);
for (User user : userList) {
log.info(user.getUserName());
}
}
}
实体类User:
@Entity
@Data
@DynamicInsert
@DynamicUpdate
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)/*配置之后返回的id不为0*/
private int id;
@Column(name="user_name")
private String userName;
@Column(name="password")
private String password;
@Column(name="create_time")
private Date createTime;
@Column(name="update_time")
private Date updateTime;
public User(String userName, String password) {
this.userName = userName;
this.password = password;
}
public User(){
}
}
说明:
1、save()方法,返回的User对象中,id是为0的。除非配置了@GeneratedValue。
2、createTime是不需要赋值的。配置数据库的时候,设置默认值为:CURRENT_TIMESTAMP。还需要在User实体类加上@DynamicInsert注解,这样插入的时候createTime就会被自动赋值。
3、updateTime也是不需要赋值的。设计数据表的使用,手动建表的话:`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP。如果是使用Navicat设计表的话,勾选“根据当前时间戳更新”。还需要在在User实体类加上@DynamicUpdate注解,这样当数据更新的时候,时间会自动更新到数据库中。
4、Mysql5.6版本之前的,是不可以使用两个默认值为CURRENT_TIMESTAMP的。低版本的需要更新,可以参考我的博客:https://blog.csdn.net/river66/article/details/104046097
5、关于@Data和@Slf4j的使用,可以参考:
https://blog.csdn.net/river66/article/details/103727367
https://blog.csdn.net/river66/article/details/103713397
除了以上的内容,JPA还有一个技术点是根据数据库表生成实体类。
1、Ctrl+Alt+Shift+S:打开Project Structure窗口,在Module中添加JPA模块。
2、选中:View-->Tool Windows-->Persistence,打开Persistence窗口。
3、选中项目,右键:Generate Persistence Mapping-->By Database Schema,然后选择数据源和生成的包路径就可以啦!
最后sql如下:
/*
Navicat MySQL Data Transfer
Source Server : superMeSql
Source Server Version : 80019
Source Host : localhost:3306
Source Database : jpa_test
Target Server Type : MYSQL
Target Server Version : 80019
File Encoding : 65001
Date: 2020-01-21 12:39:41
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`user_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`password` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'river', '123123', '2020-01-21 12:33:20', '2020-01-21 12:37:19');
INSERT INTO `user` VALUES ('2', 'river1', '123456', '2020-01-21 12:35:49', '2020-01-21 12:35:49');
INSERT INTO `user` VALUES ('3', 'river2', '123456', '2020-01-21 12:36:22', '2020-01-21 12:36:22');
INSERT INTO `user` VALUES ('4', 'river3', '123456', '2020-01-21 12:36:35', '2020-01-21 12:36:35');
INSERT INTO `user` VALUES ('5', 'river4', '123456', '2020-01-21 12:36:48', '2020-01-21 12:36:48');
觉得有用的老铁赞一下呗~