廖雪峰的零基础Java教程笔记



  • XML与JSON

    • DOM:一次性读取XML,并在内存中表示为树形结构;
    • SAX:以流的形式读取XML,使用事件回调。

    使用Jackson

    • com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.10.1
    • org.codehaus.woodstox:woodstox-core-asl:4.4.1

    使用JSON
    ...



  • JDBC编程

    JDBC查询
    executeQuery

    JDBC更新
    ...差不多...
    executeUpdate

    JDBC事务

    • Atomicity:原子性
    • Consistency:一致性
    • Isolation:隔离性
    • Durability:持久性

    详细: ACID

    JDBC Batch
    executeBatch

    JDBC连接池
    HikariCP



  • 函数式编程

    FunctionalInterface

    我们把只定义了单方法的接口称之为FunctionalInterface,用注解@FunctionalInterface标记。

    方法引用
    可利用构造函数::new

    使用Stream
    .......



  • 设计模式
    创建型模式

    • 工厂方法:Factory Method
    • 抽象工厂:Abstract Factory
    • 建造者:Builder
    • 原型:Prototype
    • 单例:Singleton

    结构型模式

    • 适配器
    • 桥接
    • 组合
    • 装饰器
    • 外观
    • 享元
    • 代理

    行为型模式

    • 责任链
    • 命令
    • 解释器
    • 迭代器
    • 中介
    • 备忘录
    • 观察者
    • 状态
    • 策略
    • 模板方法
    • 访问者


  • Web开发
    目前流行的基于Spring的轻量级JavaEE开发架构,使用最广泛的是Servlet和JMS,以及一系列开源组件。本章我们将详细介绍基于Servlet的Web开发。

    Web基础
    ...



  • Spring开发

    Spring Framework主要包括几个模块:

    • 支持IoC和AOP的容器;
    • 支持JDBC和ORM的数据访问模块;
    • 支持声明式事务的模块;
    • 支持基于Servlet的MVC开发;
    • 支持基于Reactive的Web开发;
    • 以及集成JMS、JavaMail、JMX、缓存等其他模块。

    IoC容器



  • 装配Bean
    ApplicationContext

    使用Annotation配置
    @Component,@Autowired

    使用Annotation配合自动扫描能大幅简化Spring的配置,我们只需要保证:

    • 每个Bean被标注为@Component并正确使用@Autowired注入;
    • 配置类被标注为@Configuration和@ComponentScan;
    • 所有Bean均在指定包以及子包内。

    定制Bean

    Scope

    @Component
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // @Scope("prototype")
    

    Scope注解"prototype" 每次调用生成一个实例

        @Autowired
        List<Validator> validators;
    

    如果是个List, 会注入所有类型为 Validator 的类实例, 注入次序可以用 @Order(1) 类设定

    @Autowired(required = false) 可以忽略如果没有找到的话

    创建第三方Bean

    @Configuration
    @ComponentScan
    public class AppConfig {
        // 创建一个Bean:
        @Bean
        ZoneId createZoneId() {
            return ZoneId.of("Z");
        }
    }
    

    在配置中,创建 @Bean

    初始化和销毁

    在Bean的初始化和清理方法上标记@PostConstruct@PreDestroy

    使用别名

    如果我们在@Configuration类中创建了多个同类型的Bean:可以使用 可以用@Bean("name")指定别名,也可以用@Bean+@Qualifier("name")指定别名。

    针对多个Bean, 注入时需要名称:

    @Component
    public class MailService {
    	@Autowired(required = false)
    	@Qualifier("z") // 指定注入名称为"z"的ZoneId
    	ZoneId zoneId = ZoneId.systemDefault();
        ...
    }
    

    可以指定一个默认的Bean

    @Configuration
    @ComponentScan
    public class AppConfig {
        @Bean
        @Primary
    

    使用FactoryBean

    用工厂模式创建Bean需要实现FactoryBean接口

    使用Resource
    Spring提供了一个 org.springframework.core.io.Resource

    @Component
    public class AppService {
        @Value("classpath:/logo.txt")
        private Resource resource;
    
        private String logo;
    

    注入配置
    使用 @PropertySource 读入文件, 我们使用@Value正常注入

    @Configuration
    @ComponentScan
    @PropertySource("app.properties") // 表示读取classpath的app.properties
    public class AppConfig {
        @Value("${app.zone:Z}")
        String zoneId;
    

    #{smtpConfig.host} 指的是从 Bean中读入

    @Component
    public class MailService {
        @Value("#{smtpConfig.host}")
        private String smtpHost;
    
        @Value("#{smtpConfig.port}")
        private int smtpPort;
    }
    

    使用条件装配
    @Profile

    运行时指定参数: -Dspring.profiles.active=test,master



  • 使用AOP

    拦截器类型

    • @Before:这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛异常,那么目标代码就不执行了;
    • @After:这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否抛异常,拦截器代码都会执行;
    • @AfterReturning:和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码;
    • @AfterThrowing:和@After不同的是,只有当目标代码抛出了异常时,才执行拦截器代码;
    • @Around:能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码,可以说是包含了上面所有功能。

    使用注解装配AOP

    1. 定义注解:
    @Target(METHOD)
    @Retention(RUNTIME)
    public @interface MetricTime {
        String value();
    }
    
    1. 应用case
    @Component
    public class UserService {
        // 监控register()方法性能:
        @MetricTime("register")
        public User register(String email, String password, String name) {
            ...
        }
        ...
    }
    
    1. 根据应用实现Aspect
    @Aspect
    @Component
    public class MetricAspect {
        @Around("@annotation(metricTime)")
        public Object metric(ProceedingJoinPoint joinPoint, MetricTime metricTime) throws Throwable {
            String name = metricTime.value();
            long start = System.currentTimeMillis();
            try {
                return joinPoint.proceed();
            } finally {
                long t = System.currentTimeMillis() - start;
                // 写入日志或发送至JMX:
                System.err.println("[Metrics] " + name + ": " + t + "ms");
            }
        }
    }
    

    因此,正确使用AOP,我们需要一个避坑指南:
    访问被注入的Bean时,总是调用方法而非直接访问字段;
    编写Bean时,如果可能会被代理,就不要编写public final方法。
    这样才能保证有没有AOP,代码都能正常工作。



  • 访问数据库
    使用JDBC

    • 创建全局DataSource实例,表示数据库连接池;
    • 在需要读写数据库的方法内部,按如下步骤访问数据库:
      • 从全局DataSource实例获取Connection实例;
      • 通过Connection实例创建PreparedStatement实例;
      • 执行SQL语句,如果是查询,则通过ResultSet读取结果集,如果是修改,则获得int结果。

    JdbcTemplate

    JdbcTemplate用法

    public User getUserById(long id) {
        // 注意传入的是ConnectionCallback:
        return jdbcTemplate.execute((Connection conn) -> {
            // 可以直接使用conn实例,不要释放它,回调结束后JdbcTemplate自动释放:
            // 在内部手动创建的PreparedStatement、ResultSet必须用try(...)释放:
            try (var ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) {
                ps.setObject(1, id);
                try (var rs = ps.executeQuery()) {
                    if (rs.next()) {
                        return new User( // new User object:
                                rs.getLong("id"), // id
                                rs.getString("email"), // email
                                rs.getString("password"), // password
                                rs.getString("name")); // name
                    }
                    throw new RuntimeException("user not found by id.");
                }
            }
        });
    }
    

    ...

    使用声明式事务
    PlatformTransactionManager

    使用编程的方式使用Spring事务仍然比较繁琐,更好的方式是通过声明式事务来实现。使用声明式事务非常简单,除了在AppConfig中追加一个上述定义的PlatformTransactionManager外,再加一个@EnableTransactionManagement就可以启用声明式事务:
    然后,对需要事务支持的方法,加一个@Transactional注解:
    或者更简单一点,直接在Bean的class处加上,表示所有public方法都具有事务支持

    回滚事务

    只需要抛出RuntimeException
    如果要针对Checked Exception回滚事务,需要在@Transactional注解中写出来

    事务传播

    默认的事务传播级别是REQUIRED,它满足绝大部分的需求。还有一些其他的传播级别:

    • SUPPORTS:表示如果有事务,就加入到当前事务,如果没有,那也不开启事务执行。这种传播级别可用于查询方法,因为SELECT语句既可以在事务内执行,也可以不需要事务;
    • MANDATORY:表示必须要存在当前事务并加入执行,否则将抛出异常。这种传播级别可用于核心更新逻辑,比如用户余额变更,它总是被其他事务方法调用,不能直接由非事务方法调用;
    • REQUIRES_NEW:表示不管当前有没有事务,都必须开启一个新的事务执行。如果当前已经有事务,那么当前事务会挂起,等新事务完成后,再恢复执行;
    • NOT_SUPPORTED:表示不支持事务,如果当前有事务,那么当前事务会挂起,等这个方法执行完成后,再恢复执行;
    • NEVER:和NOT_SUPPORTED相比,它不但不支持事务,而且在监测到当前有事务时,会抛出异常拒绝执行;
    • NESTED:表示如果当前有事务,则开启一个嵌套级别事务,如果当前没有事务,则开启一个新事务。

    使用:
    @Transactional(propagation = Propagation.REQUIRES_NEW)

    前提是一个线程内.

    使用DAO
    ....

    集成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>
    
    public class AppConfig {
        @Bean
        LocalSessionFactoryBean createSessionFactory(@Autowired DataSource dataSource) {
            var props = new Properties();
            props.setProperty("hibernate.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;
        }
    }
    

    @Entity, @Id, @Column...
    主键id定义的类型不是long,而是Long
    用Hibernate时,不要使用基本类型的属性,总是使用包装类型,如Long或Integer。

    @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
        public ZonedDateTime getCreatedDateTime() {
            return Instant.ofEpochMilli(this.createdAt).atZone(ZoneId.systemDefault());
        }
    
        @PrePersist
        public void preInsert() {
            setCreatedAt(System.currentTimeMillis());
        }
    }
    

    对于AbstractEntity来说,我们要标注一个@MappedSuperclass表示它用于继承。此外,注意到我们定义了一个@Transient方法,它返回一个“虚拟”的属性。因为getCreatedDateTime()是计算得出的属性,而不是从数据库表读出的值,因此必须要标注@Transient,否则Hibernate会尝试从数据库读取名为createdDateTime这个不存在的字段从而出错。

    再注意到@PrePersist标识的方法,它表示在我们将一个JavaBean持久化到数据库之前(即执行INSERT语句),Hibernate会先执行该方法,这样我们就可以自动设置好createdAt属性。

    使用Example查询

    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);
    }
    

    使用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)
        )
    );
    

    使用HQL查询

    NamedQuery

    集成JPA
    ....

    集成MyBatis
    MyBatis就是这样一种半自动化ORM框架。

    和Hibernate不同的是,MyBatis使用Mapper来实现映射,而且Mapper必须是接口。我们以User类为例,在User类和users表之间映射的UserMapper编写如下:

    public interface UserMapper {
    	@Select("SELECT * FROM users WHERE id = #{id}")
    	User getById(@Param("id") long id);
    }
    

    MyBatis提供了一个MapperFactoryBean来自动创建所有Mapper的实现类。可以用一个简单的注解来启用它:

    @MapperScan("com.itranswarp.learnjava.mapper")
    ...其他注解...
    public class AppConfig {
        ...
    }
    

    XML配置

    设计ORM




Log in to reply