Spring总结(2/2):代码
0、注解
- @Autowired:2章
1、Spring工程结构
2、组件/业务逻辑类/Bean:XxxService.java
2.1、要求
要求 |
说明 |
例子 |
类注解@Component | 注解@Component自动定义一个Bean,Bean名是首字母小写的类名;这个Bean可以在其他Bean中通过@Autowired注入 |
@Component public class UserService{ ... } |
内部注解@Autowired |
写在需要注入的Bean之前,表示注入该Bean,被注入的Bean需要有类注解@Component。 这种Bean在定义的时候,不写权限,此时默认权限为package |
@Component public class UserService { @Autowired MailService mailService;//mailService是引入的外部组件 ... } |
@Transactional |
写在Bean类的业务逻辑方法前,表明该方法需要调用AOP事务。 写在Bean类之前,表明该类的所有public方法都要调用AOP事务。 |
@Component public class UserService { // 有事务: @Transactional public User createUser(String name) { ... } // 无事务: public boolean isValidName(String name) { ... } } @Component @Transactional public class UserService { ... } |
@Autowired JdbcTemplate jdbcTemplate |
数据库操作 | |
2.2、Bean的一些额外注解
注解 |
说明 |
例子 |
@Scope |
原型Bean与单例Bean的说明,区别在于: 容器每次调用getBean(),是否会获得新的实例 |
@Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) //原型Bean public class MailSession { ... } |
@Autowired List<XxxBean>xxxBean
@Order(n) |
注入项目下所有继承自XxxBean的Bean,作为List中的元素,访问时通过for each获取List中的这些Bean。
写在上文所说的Bean之前,@Component之后,表明List中这些Bean的次序 |
@Component public class Validators { @Autowired List<Validator> validators; ... for (var validator : this.validators) { ... } } |
@Autowired(required = false) | 对容器说明,找不到Bean时忽略该Bean | |
@Bean |
注入第三方Bean(非我们自己写的组件)。 需要在配置类(AppConfig)中编写一个方法,返回这个Bean,并为该方法添加@Bean |
@Configuration @ComponentScan public class AppConfig { // 创建一个Bean: @Bean ZoneId createZoneId() { return ZoneId.of("Z"); } } |
初始化 @PostConstruct 清理 @PreDestroy |
注入Bean后,调用@PostConstruct指定的方法进行初始化; 销毁时,调用@PreDestroy指定的方法进行销毁。
实现之前还要引入依赖: javax.annotation-api |
@Component public class MailService { @Autowired(required = false) ZoneId zoneId = ZoneId.systemDefault(); @PostConstruct public void init() { System.out.println("Init mail service with zoneId = " + this.zoneId); } @PreDestroy public void shutdown() { System.out.println("Shutdown mail service"); } } |
@Bean("name"); @Bean+@Qualifier("name"); @Bean+@Primary。 |
指定Bean的别名。 注入时需要用@Qualifier("name")注入指定别名的Bean。 |
//Bean创建时指定别名 public class AppConfig { @Bean("z") ZoneId createZoneOfZ() { return ZoneId.of("Z"); } ... } //使用Bean时指定别名 @Component public class MailService { @Autowired(required = false) @Qualifier("z") // 指定注入名称为"z"的ZoneId ZoneId zoneId = ZoneId.systemDefault(); ... } |
@Profile
|
写在@Bean之后 指示Bean的运行环境,不在指定运行环境下的Bean不会被创建。 开发:native 测试:test 生产:production |
@Bean @Profile("!test") ZoneId createZoneId() { return ZoneId.systemDefault(); } @Bean @Profile("test") ZoneId createZoneIdForTest() { return ZoneId.of("America/New_York"); } |
@Conditional |
写在@Component之后 根据条件逻辑决定是否创建Bean |
@Component @Conditional(OnSmtpEnvCondition.class) public class SmtpMailService implements MailService { ... } |
2.3、例子
MailService,负责用户注册和登录成功后的邮件发送
@Component
public class MailService { private ZoneId zoneId = ZoneId.systemDefault(); public void setZoneId(ZoneId zoneId) { this.zoneId = zoneId; } public String getTime() { return ZonedDateTime.now(this.zoneId).format(DateTimeFormatter.ISO_ZONED_DATE_TIME); } public void sendLoginMail(User user) { System.err.println(String.format("Hi, %s! You are logged in at %s", user.getName(), getTime())); } public void sendRegistrationMail(User user) { System.err.println(String.format("Welcome, %s!", user.getName())); } }
UserService,用户注册与登录
@Component
public class UserService { @Autowired
MailService mailService; private List<User> users = new ArrayList<>(List.of( // users: new User(1, "bob@example.com", "password", "Bob"), // bob new User(2, "alice@example.com", "password", "Alice"), // alice new User(3, "tom@example.com", "password", "Tom"))); // tom public User login(String email, String password) { for (User user : users) { if (user.getEmail().qualsIgnoreCase(email) && user.getPassword().equals(password)) { mailService.sendLoginMail(user); return user; } } throw new RuntimeException("login failed."); } public User getUser(long id) { return this.users.stream().filter(user -> user.getId() == id).findFirst().orElseThrow(); } public User register(String email, String password, String name) { users.forEach((user) -> { if (user.getEmail().equalsIgnoreCase(email)) { throw new RuntimeException("email exist."); } }); User user = new User(users.stream().mapToLong(u -> u.getId()).max().getAsLong() + 1, email, password, name); users.add(user); mailService.sendRegistrationMail(user); return user; } }
2.4、事务类/AOP/Aspect
要求
要求 |
说明 |
例子 |
spring-aspects依赖 |
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> |
|
@Aspect |
写在@Component之前 表明该Bean是一个事务Bean,其中用@Before和@Around标注的方法将自动注入指定的位置执行 |
@Aspect @Component public class LoggingAspect { ... } |
@Before |
写在Aspect Bean中的某个方法前 表示这个类将在某个位置前执行 |
// 在执行UserService的每个方法前执行: @Before("execution(public * com.itranswarp.learnjava.service.UserService.*(..))") public void doAccessCheck() { System.err.println("[Before] do access check..."); } |
@Around |
写在Aspect Bean中的某个方法前 表明这个方法是否执行,以及执行的位置 |
// 在执行MailService的每个方法前后执行: @Around("execution(public * com.itranswarp.learnjava.service.MailService.*(..))") public Object doLogging(ProceedingJoinPoint pjp) throws Throwable { System.err.println("[Around] start " + pjp.getSignature()); Object retVal = pjp.proceed(); System.err.println("[Around] done " + pjp.getSignature()); return retVal; } |
@EnableAspectJAutoProxy |
写在@Configuration配置类之前 IoC容器看到配置类前的这个注解,就会自动查找带@Aspect的Bean,并将其中的@Around和@Before方法注入到需要注入的Bean中 |
@Configuration @ComponentScan @EnableAspectJAutoProxy public class AppConfig { ... } |
@Transactional |
写在Bean类的业务逻辑方法前,表明该方法需要调用AOP事务。 写在Bean类之前,表明该类的所有public方法都要调用AOP事务。 |
@Component public class UserService { // 有事务: @Transactional public User createUser(String name) { ... } // 无事务: public boolean isValidName(String name) { ... } } @Component @Transactional public class UserService { ... } |
3、Entity类:表示一个业务对象,是业务逻辑类操作的对象
public class User { private long id; private String email; private String password; private String name; public User(long id, String email, String password, String name) { this.id = id; this.email = email; this.password = password; this.name = name; } //getter and setter //... }
注解:
-
@Entity,Spring会自动进行Entity类与同名SQL表之间的映射。
-
@Column:写在Entity类的getter方法前,说明属性与列之间的映射规则。
与DAO的区别
DAO中定义的是,从数据库根据ID、Name进行查询的具体方法,它的返回结果是Entity类;
而Entity类则是与数据库表相对应的JavaBean,每个属性就是其中的一列。
4、Resource文件
4.1、一般Resource,如文本文件logo.txt
注解:
注解 |
说明 |
例子 |
@Value("classpath:/logo.txt") | 注入classpath下的资源文件,一般放在Resource目录下 |
@Component public class AppService { @Value("classpath:/logo.txt") private Resource resource; .... } |
@Value("file:具体路径/logo.txt") | 注入具体路径下的文件作为资源 |
注入Resource之后,可以通过Resource.getInputStream()获取输出流,避免自己搜索文件的路径:
@PostConstruct public void init() throws IOException { try (var reader = new BufferedReader( new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) { this.logo = reader.lines().collect(Collectors.joining("\n")); } }
上文代码中,reader就是一个用注入的资源文件构造的BufferedReader,通过该变量的相关方法可以访问文件中的具体事项。
4.2、配置文件:.properties与.yml
配置文件是指,各种配置项以Key=Value的形式存放的.properties文件。
4.2.1、文件读取方法:
不用@Value读入文件,而是用@Value读入具体的Key
读取方法 |
说明 |
例子 |
@ProertySource("文件名") |
用于AppConfig 读取classpath下的指定文件作为配置文件 |
@Configuration @ComponentScan @PropertySource("app.properties") // 表示读取classpath的app.properties public class AppConfig { ... } |
4.2.2、变量读取方法
读取方法 |
例子 |
@Value(XXX),XXX可以是
配合@Properties实现 |
@Value("${app.zone:Z}")
String zoneId;
|
通过创建一个简单的JavaBean持有各个配置项,持有的方法也是通过${Key}:通过该JavaBean传入配置项时,使用@Value("#{Bean.Key}")用@Component标注的Xxx,会自动持有Bean名xxx(首字母小写)。 |
//持有配置项的JavaBean @Component public class SmtpConfig { @Value("${smtp.port:25}") private int port; public int getPort() { return port; } } //通过JavaBean获取配置 @Component public class MailService { @Value("#{smtpConfig.port}") private int smtpPort; } |
4.2.3、 补充
1)配置文件中的不同Key之间,也可以通过${Key}互相引用
my.name=航歌
my.age=100
my.info=name:${my.name} age:${my.age}
2)通过${random}生成各种类型的随机值
my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number.less.than.ten=${random.int(10)}
my.number.in.range=${random.int[1024,65536]}
3)配置文件的优先级
Spring Boot 项目中的 application.properties 配置文件一共可以出现在如下 4 个位置(优先级逐渐降低):
- 项目根目录下的 config 文件夹
- 项目根目录下
- classpath 下的 config 文件夹
- classpath 下
如果这 4 个地方都有 application.properties 文件,加载的优先级就是从 1 到 4 依次降低,Spring Boot 将按照这个优先级查找配置信息。
5、Appconfig.java
要求:
要求 |
说明 |
例子 |
@Configuration |
表示这是一个配置类 | |
@ComponentScan |
有了这个注解,容器就会自动搜索当前类所在的包和子包,把所有注解@Componenet的Bean自动创建出来,并根据@Autowired进行装配。 | |
@EnableAspectJAutoProxy |
自动查找带@Aspect注解的Bean,根据其中的@Before和@Around,将AOP注入指定的Bean中 |
|
@EnableTransactionManagement |
启动声明式事务,启动该项后,自动启动AOP,所以不用再写一遍注解@EnableAspectJAutoProxy |
|
@Bean HikariDataSource @Bean JdbcTemplate @Component DatabaseInitializer @BeanPlatform TransactionManager |
JDBC |
|
@Bean DataSource @Bean LocalSessionFactoryBean @Bean HibernateTemplate @Bean HibernateTransactionManager |
Hibernate | |
main() |
启动Spring事务,调用各业务逻辑类处理业务 |
|
AppConfig.java必须放在最顶层,与其他Bean的所在子包位于同一目录。 |
||
上下文环境的配置通过context变量处理 | 这是一个AnnotationConfigApplicationContext实例,通常放在main方法第一行实现。 |
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); |
Bean通过context.getBean(XxxService.class)获取 |
这些Bean必须要用@Component标注,以保证可以扫描到。 且Bean所在的包目录要与AppConfig.java同级。 |
UserService userService = context.getBean(UserService.class);
|
@PropertySource("xxx.properties") @Value("jdbc.url") |
获取classpath(一般是resource目录)下的配置文件 通过4.2.1节所说的方式,注入相关配置 |
|
@Configuration @ComponentScan public class AppConfig { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); UserService userService = context.getBean(UserService.class); User user = userService.login("bob@example.com", "password"); System.out.println(user.getName()); } }
6、JDBC
要求
要求 |
说明 |
例子 |
@Bean HikariDataSource |
Appconfig中创建的Bean 代表一个DataSource实例,即连接池 |
@Bean DataSource createDataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl(jdbcUrl); config.setUsername(jdbcUsername); config.setPassword(jdbcPassword); config.addDataSourceProperty("autoCommit", "true"); config.addDataSourceProperty("connectionTimeout", "5"); config.addDataSourceProperty("idleTimeout", "60"); return new HikariDataSource(config); } |
@Bean JdbcTemplate |
Appconfig中创建的Bean 用于操作JDBC 需要在所有需要数据库操作的Bean中,通过@Autowired注入 |
@Bean JdbcTemplate createJdbcTemplate(@Autowired DataSource dataSource) { return new JdbcTemplate(dataSource); } |
@Component DatabaseInitializer |
一个外部Bean 在Spring启动时初始化数据库表User(非必须的Bean) |
@Component public class DatabaseInitializer { @Autowired JdbcTemplate jdbcTemplate; @PostConstruct public void init() { jdbcTemplate.update("CREATE TABLE IF NOT EXISTS users (" // + "id BIGINT IDENTITY NOT NULL PRIMARY KEY, " // + "email VARCHAR(100) NOT NULL, " // + "password VARCHAR(100) NOT NULL, " // + "name VARCHAR(100) NOT NULL, " // + "UNIQUE (email))"); } }
|
@Autowired
JdbcTemplate jdbcTemplate;
|
在需要数据库操作的业务逻辑类XxxService中注入jdbcTemplate |
@Component public class DatabaseInitializer { @Autowired JdbcTemplate jdbcTemplate; ... //用jdbcTemplate操作数据库 } @Component public class UserService { @Autowired JdbcTemplate jdbcTemplate; ... //用jdbcTemplate操作数据库 } |
T execute(ConnectionCallback<T> action) T execute(String sql , PreparedStatementCallback<T> action) T queryForObject(String sql , Object [ ] args , RowMapper<T> rowMapper) |
查询,用jdbcTemplate.xxx()调用 具体请参考 |
|
int update(SQL , v1 , v2 , ...) |
增删改,都用该方法 具体请参考Spring总结(1/2)8.2.2节 |
|
PlatformTransactionManager |
Appconfig中的一个Bean 作用是事务管理器 |
@Bean PlatformTransactionManager createTxManager(@Autowired DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } |
TransanctionStatus | 事务 |
TransactionStatus tx = null; try { // 开启事务: tx = txManager.getTransaction(new DefaultTransactionDefinition()); // 相关JDBC操作: jdbcTemplate.update("..."); jdbcTemplate.update("..."); // 提交事务: txManager.commit(tx); } catch (RuntimeException e) { // 回滚事务: txManager.rollback(tx); throw e; } |
@Transactional |
对于需要开启事务的业务逻辑Bean的方法,需要添加该注解。 直接对业务逻辑Bean添加该注解,表示所有方法都支持事务。 |
@Component public class UserService { // 此public方法自动具有事务支持: @Transactional public User register(String email, String password, String name) { ... } } @Component @Transactional public class UserService { ... } |
throw new RuntimeException("info") | 事务回滚,只需要在事务方法总抛出RuntimeException(及其子类) |
@Transactional public buyProducts(long productId, int num) { ... if (store < num) { // 库存不够,购买失败: throw new IllegalArgumentException("No enough products"); } ... } |
@Transactional(rollbackFor = {A.class,B.class,...}) @Transactional(propagation = 级别) |
指定在抛出某些具体的Exception时,才回滚事务 定义事务传播级别 |
@Transactional(rollbackFor = {RuntimeException.class, IOException.class}) public buyProducts(long productId, int num) throws IOException { ... } |
自定义业务异常类 |
继承自RuntimeException 这样可以不必声明任何特殊异常就让声明式事务正常工作 |
public class BusinessException extends RuntimeException { ... } public class LoginException extends BusinessException { ... } public class PaymentException extends BusinessException { ... } |
依赖
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.0.RELEASE</version> </dependency> <dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.4.2</version> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.5.0</version> </dependency> </dependencies>
7、Dao
一个经典Dao模型:注意与经典JavaBean的getter、setter区分,这里是数据修改(增删改查)的常用方法
public class UserDao{ @Autowired JdbcTemplate jdbcTemplate; User getById(long id){ ... } List<User> getUsers(int pages){ ... } User createUser(User user){ ... } User updateUser(User user){ ... } void deleteUser(user user){ ... } }
要求
要求 |
说明 |
例子 |
JdbcDaoSupport |
Spring提供的一个Dao模型,是一个抽象类。 子类从它继承后,调用getJdbcTemplate()获得JdbcTemplate实例 |
public abstract class JdbcDaoSupport extends DaoSupport { private JdbcTemplate jdbcTemplate; public final void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; initTemplateConfig(); } public final JdbcTemplate getJdbcTemplate() { return this.jdbcTemplate; } ... } |
AbstractDao | 并非Spring提供,而是自己写的,专门用于注入JdbcTemplate,这也是一个抽象类。 |
public abstract class AbstractDao extends JdbcDaoSupport @Autowired private JdbcTemplate jdbcTemplate; @PostConstruct public void init(){ super.setJdbcTemplate(jdbcTemplate); } } |
AbstractDao<T> |
泛型Dao,其中增添了更多样版代码,同时实现了更多的通用方法如 getById(),getAll(),deleteById() |
public abstract class AbstractDao<T> extends JdbcDaoSupport{ private String table; private Class<T> entityClass; private RowMapper<T> rowMapper; public AbstractDao(){ //获取当前类型的泛型类型 this.entityClass = getParameterizedTpe(); this.table = this.entityClass.getSimpleName().toLowerCase()+"s"; this.rowMapper = new BeanPropertyRowMapper<>(entityClass); } public T getById(long id){ return getJdbcTemplate().queryForObject("SELECT * FROM "+ table + " WHERE id = ? ",this.rowMapper,id); } public List<T> getAll(int pageIndex){ int limit = 100; int offset = limit * (pageIndex - 1); return getJdbcTemplate().query("SELECT * FROM "+ table + " LIMIT ? OFFSET ?", new Object[]{limit,offset},this.rowMapper); } public void deleteById(long id){ getJdbcTemplate().update("DELETE FROM "+table+"WHERE id = ?",id); } ... }
|
XxxDao |
真正的Dao类,继承自AbstractDao<Xxx>用于数据处理(增删改查)。 可以直接调用getJdbcTemplate()获得JdbcTemplate实例 |
@Component @Transactional public class UserDao extends AbstractDao<User>{ //已经有了: //User getById(long) //List<User> getAll(int) //void deleteById(long) } @Component @Transactional public class BookDao extends AbstractDao<Book>{ //已经有了: //Book getById(long) //List<Book> getAll(int) //void deleteById(long) } |
@Component @Transactional |
Dao所需的事务注解 |
8、Hibernate
依赖:org.hibernate
<!-- JDBC驱动,这里使用HSQLDB --> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.5.0</version> </dependency> <!-- JDBC连接池 --> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.4.2</version> </dependency> <!-- Hibernate --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.4.2.Final</version> </dependency> <!-- Spring Context和Spring ORM --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.2.0.RELEASE</version> </dependency>
要求
要求 |
说明 |
代码 |
@Bean DataSource | AppConfig中的一个Bean,表示DataSource(及其子类),即连接池 |
@Bean
DataSource createDataSource(){
...
}
|
@Bean LocalSessionFactoryBean | AppConfig中的一个Bean,用于创建SessionFactory |
@Bean LocalSessionFactoryBean createSessionFactory(@Autowired DataSource dataSource){ var props = new Properties(); props.setProperty("bibernate.hbm2ddl.auto","update");//生产环境不要使用 props.setProperty("hibernate.dialect","org.hibernate.dialect.HSQLDialect"); props.setProperty("hibernate.show_sql","true"); var sessionFactoryBean = new LocalSessionFactoryBean(); sessionFactoryBean.setDataSource(dataSource); //扫描指定的package获取所有的entity class: sessionFactoryBean.setPackagesToScan("com.itranswarp.learnjava.entity"); sessionFactoryBean.setHibernateProperties(props); return sessionFactoryBean; }
|
SessionFactory |
每次连接数据库时,用于创建新的Session。 即从连接池中获取一个新的Connection |
|
@Bean HibernateTemplate | Spring提供的用于简化Hibernate操作的工具类 |
@Bean HibernateTemplate createHibernateTemplate(@Autowired SessionFactory sessionFactory){ return new HibernateTemplate(sessionFactory); } |
@Bean HibernateTransactionManager |
继承自PlatformTransactionManager,按照第6章所说,这是一个事务管理器,用于启动声明式服务。 |
@Bean PlatformTransactionManager createTxManager(@Autowired SessionFactory sessionFactory){ return new HibernateTransactionManager(sessionFactory); } |
@Entity |
写在数据表对应的JavaBean前的注解。 表示要在Xxx类与xxx表之间进行映射。 |
@Entity public class User{ ... } |
@Column( nullable = false , unique = true , updatable = false, length = 100 ) |
写在JavaBean的getter方法前,表示类字段与表中某列间的映射。 nullable:是否允许为NULL; updatable:该列是否允许UPDATE; unique:是否唯一不重复; length:String类型的列每个元素的长度 |
@Column(nullable = false, unique = true, length = 100) public String getEmail() { ... } @Column(nullable = false, length = 100) public String getPassword() { ... } @Column(nullable = false, length = 100) public String getName() { ... } @Column(nullable = false, updatable = false) public Long getCreatedAt() { ... } |
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) |
写在JavaBean的某一个getter方法前,指示主键。 @GeneratedValue标识自增主键。 |
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(nullable = false, updatable = false) public Long getId() { ... }
|
这个JavaBean的getter的返回值必须是包装类型 |
不能是基本类型 | |
@MappedSuperclass | 标注AbstractEntity,这是提取不同JavaBean的共同属性构成的一个抽象Entity类,专门用于继承。 |
@MappedSuperclass public abstract class AbstractEntity{ private Long id; private Long createdAt; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(nullable = false , updatable = false) public Long getId() {...} @Column(nullable = false, updatable = false) public Long getCreatedAt() { ... } }
|
@Transient |
用于某个getter方法前,表明该方法并非从数据库提取属性的值,而是通过计算获得。 需要引入依赖 javax.persistence |
@Transient public ZonedDateTime getCreatedDateTime(){ return Instant.ofEpochMilli(this.createAt).atZone(ZoneId.systemDefault()); } |
@Persist |
标注某个普通方法。 该方法将在JavaBean持久化到数据库之前执行。 需要引入依赖 javax.persistence |
@PrePersist public void preInsert(){ setCreatedAt(System.currentTimeMillis()); } |
从AbstractEntity继承而来的Entity |
一个Entity,继承自@MappedSuperclass,只需要写特有字段的getter方法即可 |
@Entity public class User extends AbstractEntity { @Column(nullable = false, unique = true, length = 100) public String getEmail() { ... } @Column(nullable = false, length = 100) public String getPassword() { ... } @Column(nullable = false, length = 100) public String getName() { ... } } |
利用Hibernate对user表进行增删改查(步骤):
①往业务逻辑类XxxService中,注入HibernateTemplate:
@Component @Transactional public class UserService{ @Autowired HibernateTemplate hibernateTemplate; }
②持久化/INSERT:hibernateTemplate.save(user);
持久化的意思是,把一个JavaBean实例,作为表的一行插入表中。
public User register(String email, String password, String name) { // 创建一个User对象: User user = new User(); // 设置好各个属性: user.setEmail(email); user.setPassword(password); user.setName(name); // 不要设置id,因为使用了自增主键 // 保存到数据库: hibernateTemplate.save(user); // 现在已经自动获得了id: System.out.println(user.getId()); return user; }
③删除/DELETE:get(User.class,id) + delete(user)
根据ID进行删除
public boolean deleteUser(Long id) { User user = hibernateTemplate.get(User.class,id); if (user != null){ hibernateTemplate.delete(user); return true; } return false; }
④更新/UPDATE:load(User.class,id) + update(user)
先根据ID获取某行记录作为一个user,再对该user进行更新:
public void updateUser(Long id, String name) { User user = hibernaateTemplate.load(User.class , id); user.setName(name); hibernateTemplate.update(user); }
⑤条件查询WHERE
SELECT * FROM user WHERE email = ? AND password = ?
a、Example查询:findByExample(user)
给出一个User实例,Hibernate把该实例的所有非null属性拼成WHERE条件:
public User login(String email , String password){ User example = new User(); example.setEmail(email); example.setPassword(password); List<User> list = hibernateTemplate.findByExample(example); return list.isEmpty() ? null : list.get(0); }
因为example实例只有email与password两个属性为非null,所以最终生成的WHERE语句为WHERE email = ? AND password = ?。
基本类型字段总是会加入WHERE条件!
b、Criteria查询:findByCriteria(DetachedCriteria)
public User login(String email, String password) { DetachedCriteria criteria = DetachedCriteria.forClass(User.class); criteria.add(Restrictions.eq("email", email)) .add(Restrictions.eq("password", password)); List<User> list = (List<User>) hibernateTemplate.findByCriteria(criteria); return list.isEmpty() ? null : list.get(0); }
和findByExample()相比,findByCriteria()可以组装出更加灵活的WHERE条件,例如:
SELECT * FROM user WHERE (email = ? OR name = ?) AND password = ?
该查询没法用findByExample()实现,用Criteria查询可以实现如下:
DetachedCriteria criteria = DetachedCriteria.forClass(User.class); criteria.add( Restrictions.and( Restrictions.or( Restrictions.eq("email", email), Restrictions.eq("name", email) ), Restrictions.eq("password", password) ) );
只要组织好Restrictions的嵌套关系,Criteria查询可以实现任意复杂的查询。
c、HQL查询:find(SQL,para1,para2,...)
最后一种常用的查询是直接编写Hibernate内置的HQL查询:
String SQL= "FROM User WHERE email = ? AND password = ?"; List<User> list = (List<User>) hibernateTemplate.find(SQL,email,password);
和SQL相比,HQL使用类名和属性名,由Hibernate自动转换为实际表名和列名,详细的HQL语法可以参考Hibernate文档。
d、@NamedQuires:findByNamedQuery(Queryname,para1,para2,...)
javax.persistence.NamedQuery
给查询起个名字,然后保存在注解中。
使用NamedQuery时,我们要现在User类标注:
@NamedQueries( @NamedQuery( // 查询名称: name = "login", // 查询语句: query = "SELECT u FROM User u WHERE u.email=?0 AND u.password=?1" ) ) @Entity public class User extends AbstractEntity { ... }
与HQL的区别在于,占位符使用?0、?1,并且索引是从0开始的。
使用NamedQuery用findByNamedQuery(Queryname,para1,para2,...)只需要引入查询名和参数:
public User login(String email , String password){ List<User> list = (List<User>) hibernateTemplate.findByNamedQuery("login",email,password); return list.isEmpty() ? null : list.get(0)l; }
9、JPA
依赖:javax.persistence
- org.springframework:spring-context:5.2.0.RELEASE
- org.springframework:spring-orm:5.2.0.RELEASE
- javax.annotation:javax.annotation-api:1.3.2
- org.hibernate:hibernate-core:5.4.2.Final
- com.zaxxer:HikariCP:3.4.2
- org.hsqldb:hsqldb:2.5.0
JPA是个接口,需要选择实现类(JPA提供方)如Hibernate、EclipseLink。本节以Hibernate作为JPA实现。
要求:
要求 |
说明 |
例子 |
@Bean DataSource | AppConfig中的一个Bean,表示DataSource(及其子类),即连接池 |
@Configuration @ComponentScan @EnableTransactionManagement @PropertySource("jdbc.properties") public class AppConfig{ @Bean DataSource createDataSource(){ ... } }
|
@Bean
LocalContainerEntityManagerFactoryBean |
AppConfig中的一个Bean,用于创建
EntityManagerFactory |
|
@Bean PlatformTransactionManager |
AppConfig中的一个Bean,用于声明式事务 通常返回JpaTransactionManager的实例 |
|
Spring + Hibernate实现JPA时,不用XML | ||
@Component @Transactional |
用于业务逻辑类XxxService之前 |
@Component @Transactional public class UserService{ @PersistenceContext EntityManager em; } |
@PersistenceContext | 用于EntityManager之前,表示注入 | |
EntityManager |
业务逻辑类XxxService中注入的实例,代表一个Connection。 通过该实例的各项方法实现对数据库的操作 |
|
@Entity |
写在Entity类之前,表示具体的表格对象,如User |
@Entity public class User { ... } |
JPQL查询
语法和HQL差不多:
-
TypeQuery<User> em.createQuery(SQL,User.class)
-
query.setParameter(占位符,para1,para2,...)
-
List<User> query.getResultList()
占位符用:占位符的形式
public User getUserByEmail(String email) { // JPQL查询: String SQL = "SELECT u FROM User u WHERE u.email = :e"; TypedQuery<User> query = em.createQuery(SQL, User.class); query.setParameter("e", email); List<User> list = query.getResultList(); if (list.isEmpty()) { throw new RuntimeException("User not found by email."); } return list.get(0); }
NamedQuery查询
定义
通过注解标注在User类前(非XxxService前)
@NamedQueries( @NamedQuery( name = "login", query = "SELECT u FROM User u WHERE u.email=:e AND u.password=:p" ) ) @Entity public class User { ... }
操作数据库
对数据库进行增删改操作,可以分别使用persist()、remove()、merge()方法,参数均为Entity Bean本身。
10、Mybatis
依赖:
- org.mybatis:mybatis:3.5.4
- org.mybatis:mybatis-spring:2.0.4
要求:
要求 |
说明 |
例子 |
@Bean DataSource | AppConfig中的Bean,作用同前 | |
@Bean SqlSessionFactoryBean | AppConfig中的Bean,Mybatis的核心 | |
@Bean PlatformTransactionManager |
AppConfig中的Bean,是一个 DataSourceTransactionManager实例 代表事务管理器 |
|
interface UserMapper |
一个接口,用于实现表与Entity间的映射。 其中不仅要定义访问user表的接口方法,还要明确写出查询的SQL语句 |
public interface UserMapper{ @Select("SELECT * FROM users WHERE id = #{id}") User getById(@Param("id") long id); } |
@Select | 接口Mapper中定义的查询SQL语句的注解。 | |
@Insert | 接口Mapper中定义的插入SQL语句的注解。 |
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") @Insert("INSERT INTO users (email, password, name, createdAt) VALUES (#{user.email}, #{user.password}, #{user.name}, #{user.createdAt})") void insert(@Param("user") User user); |
@Options | 插入后,如果希望获得自增主键,则添加该注解。 | |
@Update | 接口Mapper中定义的更新SQL语句的注解。 |
@Update("UPDATE users SET name = #{user.name}, createdAt = #{user.createdAt} WHERE id = #{user.id}") void update(@Param("user") User user); |
@Delete | 接口Mapper中定义的删除SQL语句的注解。 |
@Delete("DELETE FROM users WHERE id = #{id}") void deleteById(@Param("id") long id); |
@MapperScan |
写于AppConfig之前 根据该注解,Mybatis会自动扫描指定包,并创建Mapper实现类(通过注入使用) |
@MapperScan("com.itranswarp.learn.java.mapper") ...其他注解... public class AppConfig{ ... } |
@Autowired UserMapper userMapper |
写在XxxService中 注入之前所写的Mapper,使用其中的各种方法 |
@Component @Transactional public class UserService { // 注入UserMapper: @Autowired UserMapper userMapper; public User getUserById(long id) { // 调用Mapper方法: User user = userMapper.getById(id); if (user == null) { throw new RuntimeException("User not found by id."); } return user; } } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性