spring访问数据库

spring访问数据库

JAVA使用JDBC

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

Spring使用JDBC

第一步先引入maven:
引入jdbc
<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>

第二步
@Configuration
@ComponentScan
@PropertySource("jdbc.properties")
public class AppConfig {

    @Value("${jdbc.url}")
    String jdbcUrl;

    @Value("${jdbc.username}")
    String jdbcUsername;

    @Value("${jdbc.password}")
    String jdbcPassword;

    @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);
    }
    
    //使用spring提供的JbdcTemplate来创建数据
    @Bean
    JdbcTemplate createJdbcTemplate(@Autowired DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}
第三步使用JdbcTemplate
有这几种使用方式
1. execute(ConnectionCallback<T> action) 这就是jdbc的connection的用法 都能用
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.");
    }
}

2. execute(String sql, PreparedStatementCallback<T> action)
jdbcTemplate.execute("SELECT * FROM users WHERE name = ?", (PreparedStatement ps) -> {
        // PreparedStatement实例已经由JdbcTemplate创建,并在回调后自动释放:
        ps.setObject(1, name);
        try (var rs = ps.executeQuery()) {
            if (rs.next()) {
                ...
            }
            throw new RuntimeException("user not found by id.");
        }
    });

3. queryForObject(String sql, Object[] args, RowMapper<T> rowMapper)
jdbcTemplate.queryForObject("SELECT * FROM users WHERE email = ?", new Object[] { xxx@qq.com },
(ResultSet rs, int rowNum) -> {
    // 将ResultSet的当前行映射为一个JavaBean:
    return new User( // new User object:
            rs.getLong("id"), // id
            rs.getString("email"), // xxx@qq.com
            rs.getString("password"), // password
            rs.getString("name")); // name
});
jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", null, (ResultSet rs, int rowNum) -> {
        // SELECT COUNT(*)查询只有一列,取第一列数据:
        return rs.getLong(1);
});

4. query返回多条数据
jdbcTemplate.query("SELECT * FROM users LIMIT ? OFFSET ?", new Object[] { limit, offset },
            new BeanPropertyRowMapper<>(User.class))
那么BeanPropertyRowMapper就可以直接把一行记录按列名转换为JavaBean 会自动映射

5. insert获取自增长的id 跟jdbc那的很相似  多了KeyHolder
public User register(String email, String password, String name) {
    // 创建一个KeyHolder:
    KeyHolder holder = new GeneratedKeyHolder();
    if (1 != jdbcTemplate.update(
        // 参数1:PreparedStatementCreator
        (conn) -> {
            // 创建PreparedStatement时,必须指定RETURN_GENERATED_KEYS:
            var ps = conn.prepareStatement("INSERT INTO users(email,password,name) VALUES(?,?,?)",
                    Statement.RETURN_GENERATED_KEYS);
            ps.setObject(1, email);
            ps.setObject(2, password);
            ps.setObject(3, name);
            return ps;
        },
        // 参数2:KeyHolder
        holder)
    ) {
        throw new RuntimeException("Insert failed.");
    }
    // 从KeyHolder中获取返回的自增值:
    return new User(holder.getKey().longValue(), email, password, name);
}

使用声明式事物

JDBC的事物是这样的

Connection conn = openConnection();
try {
    // 关闭自动提交:
    conn.setAutoCommit(false);
    // 执行多条SQL语句:
    insert(); update(); delete();
    // 提交事务:
    conn.commit();
} catch (SQLException e) {
    // 回滚事务:
    conn.rollback();
} finally {
    conn.setAutoCommit(true);
    conn.close();
}

那spring 的事物该如何使用呢? 
Spring提供了一个PlatformTransactionManager来表示事务管理器,所有的事务都由它负责管理
事务由TransactionStatus表示
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;
}

为什么要抽象出PlatformTransactionManager 和 TransactionStatus ?

原因是JavaEE除了提供JDBC事务外,它还支持分布式事务JTA(Java Transaction API)。
分布式事务是指多个数据源(比如多个数据库,多个消息系统)要在分布式环境下实现事务的时候,
应该怎么实现。分布式事务实现起来非常复杂,简单地说就是通过一个分布式事务管理器实现两阶段提交,
但本身数据库事务就不快,基于数据库事务实现的分布式事务就慢得难以忍受,所以使用率不高
Spring为了同时支持JDBC和JTA两种事务模型,
就抽象出PlatformTransactionManager。因为我们的代码只需要JDBC事务
 
@Configuration
@ComponentScan
@PropertySource("jdbc.properties")
public class AppConfig {
    ...
    @Bean
    PlatformTransactionManager createTxManager(@Autowired DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

使用编程的方式使用Spring事务仍然比较繁琐,更好的方式是通过声明式事务来实现。使用声明式事务非常简单,
除了在AppConfig中追加一个上述定义的PlatformTransactionManager外,
再加一个@EnableTransactionManagement就可以启用声明式事务:
@Configuration
@ComponentScan
@EnableTransactionManagement // 启用声明式
@PropertySource("jdbc.properties")
public class AppConfig {
    ...
}
然后,对需要事务支持的方法,加一个@Transactional注解:

@Component
public class UserService {
    // 此public方法自动具有事务支持:
    @Transactional
    public User register(String email, String password, String name) {
       ...
    }
}
或者更简单一点,直接在Bean的class处加上,表示所有public方法都具有事务支持:

实现原理:
public class UserService$$EnhancerBySpringCGLIB extends UserService {
    UserService target = ...
    PlatformTransactionManager txManager = ...

    public User register(String email, String password, String name) {
        TransactionStatus tx = null;
        try {
            tx = txManager.getTransaction(new DefaultTransactionDefinition());
            target.register(email, password, name);
            txManager.commit(tx);
        } catch (RuntimeException e) {
            txManager.rollback(tx);
            throw e;
        }
    }
    ...
}

回滚事务 定义好程序里的异常很利于事物回滚的控制

默认情况下,如果发生了RuntimeException,Spring的声明式事务将自动回滚。在一个事务方法中,
如果程序判断需要回滚事务,只需抛出RuntimeException,例如:

@Transactional
public buyProducts(long productId, int num) {
    ...
    if (store < num) {
        // 库存不够,购买失败:
        throw new IllegalArgumentException("No enough products");
    }
    ...
}
如果要针对Checked Exception回滚事务,需要在@Transactional注解中写出来:

@Transactional(rollbackFor = {RuntimeException.class, IOException.class})
public buyProducts(long productId, int num) throws IOException {
    ...
}
上述代码表示在抛出RuntimeException或IOException时,事务将回滚。

为了简化代码,我们强烈建议业务异常体系从RuntimeException派生,这样就不必声明任何特殊异常即可让Spring的声明式事务正常工作:

public class BusinessException extends RuntimeException {
    ...
}

public class LoginException extends BusinessException {
    ...
}

public class PaymentException extends BusinessException {
    ...
}

事务边界 和 事物传播

例子分析
@Component
public class BonusService {
    @Transactional
    public void addBonus(long userId, int bonus) { // 事务开始
       ...
    } // 事务结束
}
@Component
public class UserService {
    @Autowired
    BonusService bonusService;

    @Transactional
    public User register(String email, String password, String name) {
        // 插入用户记录:
        User user = jdbcTemplate.insert("...");
        // 增加100积分: 
        这个时候事物的边界是什么 
        bonusService.addBonus(user.id, 100);
    }
}
以上结论是会先判断当前方法有没有事物,如果有事物就加入到当前的事物,默认的事物级别是REQUIRED

事物级别说明
SUPPORTS:表示如果有事务,就加入到当前事务,
如果没有,那也不开启事务执行。这种传播级别可用于查询方法,因为SELECT语句既可以在事务内执行,也可以不需要事务;

MANDATORY:表示必须要存在当前事务并加入执行,否则将抛出异常。
这种传播级别可用于核心更新逻辑,比如用户余额变更,它总是被其他事务方法调用,不能直接由非事务方法调用;

REQUIRES_NEW:表示不管当前有没有事务,都必须开启一个新的事务执行。
如果当前已经有事务,那么当前事务会挂起,等新事务完成后,再恢复执行;

NOT_SUPPORTED:表示不支持事务,如果当前有事务,那么当前事务会挂起,等这个方法执行完成后,再恢复执行;

NEVER:和NOT_SUPPORTED相比,它不但不支持事务,而且在监测到当前有事务时,会抛出异常拒绝执行;

NESTED:表示如果当前有事务,则开启一个嵌套级别事务,如果当前没有事务,则开启一个新事务

@Transactional(propagation = Propagation.REQUIRES_NEW)
public Product createProduct() {
    ...
}

事务能正确传播的前提是,方法调用是在一个线程内才行

使用DAO

通常是Web层调用业务层,业务层调用数据访问层

例如:
public class UserDao {

    @Autowired
    JdbcTemplate jdbcTemplate;

    User getById(long id) {
        ...
    }

    List<User> getUsers(int page) {
        ...
    }

    User createUser(User user) {
        ...
    }

    User updateUser(User user) {
        ...
    }

    void deleteUser(User user) {
        ...
    }
}

Spring提供了一个JdbcDaoSupport类,用于简化DAO的实现
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;
    }

    ...
}
它的意图是子类直接从JdbcDaoSupport继承后,可以随时调用getJdbcTemplate()获得JdbcTemplate的实例

那么问题来了:因为JdbcDaoSupport的jdbcTemplate字段没有标记@Autowired,所以,子类想要注入JdbcTemplate,还得自己想个办法:

@Component
@Transactional
public class UserDao extends JdbcDaoSupport {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @PostConstruct
    public void init() {
        super.setJdbcTemplate(jdbcTemplate);
    }
}

public abstract class AbstractDao extends JdbcDaoSupport {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @PostConstruct
    public void init() {
        super.setJdbcTemplate(jdbcTemplate);
    }
}


Hibernate

是一个比较成熟的ORM框架

public class AppConfig {
    @Bean
    LocalSessionFactoryBean createSessionFactory(@Autowired DataSource dataSource) {
        var sessionFactoryBean = new LocalSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        
        // 扫描指定的package获取所有entity class:
        sessionFactoryBean.setPackagesToScan("com.itranswarp.learnjava.entity");
        var props = new Properties();
        props.setProperty("hibernate.hbm2ddl.auto", "update"); // 生产环境不要使用
        props.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
        props.setProperty("hibernate.show_sql", "true");
        sessionFactoryBean.setHibernateProperties(props);
        return sessionFactoryBean;
    }
}
var sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
注意我们在定制Bean中讲到过FactoryBean,LocalSessionFactoryBean是一个FactoryBean,
它会再自动创建一个SessionFactory,在Hibernate中,Session是封装了一个JDBC Connection的实例,
而SessionFactory是封装了JDBC DataSource的实例,即SessionFactory持有连接池,每次需要操作数据库的时候,
SessionFactory创建一个新的Session,相当于从连接池获取到一个新的Connection。
SessionFactory就是Hibernate提供的最核心的一个对象,
但LocalSessionFactoryBean是Spring提供的为了让我们方便创建SessionFactory的类。

紧接着,我们还需要创建HibernateTemplate以及HibernateTransactionManager:

public class AppConfig {
    @Bean
    HibernateTemplate createHibernateTemplate(@Autowired SessionFactory sessionFactory) {
        return new HibernateTemplate(sessionFactory);
    }

    @Bean
    PlatformTransactionManager createTxManager(@Autowired SessionFactory sessionFactory) {
        return new HibernateTransactionManager(sessionFactory);
    }
}
这两个Bean的创建都十分简单。HibernateTransactionManager是配合Hibernate使用声明式事务所必须的,
而HibernateTemplate则是Spring为了便于我们使用Hibernate提供的工具类,不是非用不可,但推荐使用以简化代码。

映射类的创建
@Entity
@Table(name="users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false, updatable = false)
    public Long getId() { ... }

    @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定义的类型不是long,而是Long。
这是因为Hibernate如果检测到主键为null,
就不会在INSERT语句中指定主键的值,而是返回由数据库生成的自增值,
否则,Hibernate认为我们的程序指定了主键的值,会在INSERT语句中直接列出。long型字段总是具有默认值0,
因此,每次插入的主键值总是0,导致除第一次外后续插入都将失败。

hibenate的增删改查
@Autowired
HibernateTemplate hibernateTemplate;
hibernateTemplate.save(user);
hibernateTemplate.delete(user);
hibernateTemplate.update(user);

User user = hibernateTemplate.get(User.class, id);
hibernateTemplate.update(user);

load()和get()都可以根据主键加载记录,它们的区别在于,当记录不存在时,get()返回null,而load()抛出异常。
查询有两种方式:
1. 使用example
User example = new User();
example.setEmail(email);
example.setPassword(password);
List<User> list = hibernateTemplate.findByExample(example);
2. 使用criteria
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);
criteria的高级用法
SELECT * FROM user WHERE (email = ? OR name = ?) AND password = ?

criteria.add(
    Restrictions.and(
        Restrictions.or(
            Restrictions.eq("email", email),
            Restrictions.eq("name", email)
        ),
		Restrictions.eq("password", password)
    )
);
3. hql
List<User> list = (List<User>) hibernateTemplate.find("FROM User WHERE email=? AND password=?", email, password);
和SQL相比,HQL使用类名和属性名,由Hibernate自动转换为实际的表名和列名

除了可以直接传入HQL字符串外,Hibernate还可以使用一种NamedQuery,它给查询起个名字,然后保存在注解中
@NamedQueries(
    @NamedQuery(
        // 查询名称:
        name = "login",
        // 查询语句:
        query = "SELECT u FROM User u WHERE u.email=?0 AND u.password=?1"
    )
)
public User login(String email, String password) {
    List<User> list = (List<User>) hibernateTemplate.findByNamedQuery("login", email, password);
    return list.isEmpty() ? null : list.get(0);
}
直接写HQL和使用NamedQuery各有优劣。前者可以在代码中直观地看到查询语句,后者可以在User类统一管理所有相关查询。


4. 使用Hibernate原生接口
使用Hibernate的原生接口实际上总是从SessionFactory出发,它通常用全局变量存储,在HibernateTemplate中以成员变量被注入。

void operation() {
    Session session = null;
    boolean isNew = false;
    // 获取当前Session或者打开新的Session:
    try {
        session = this.sessionFactory.getCurrentSession();
    } catch (HibernateException e) {
        session = this.sessionFactory.openSession();
        isNew = true;
    }
    // 操作Session:
    try {
        User user = session.load(User.class, 123L);
    }
    finally {
        // 关闭新打开的Session:
        if (isNew) {
            session.close();
        }
    }
}

集成JPA Java Persistence API

JPA从本质上讲就是java组织定义的一些orm的标准接口。hibernate就是jpa这个接口的具体实现
跟JDBC和mysql的关系是一样的

在Spring中集成JPA要选择一个实现,可以选择Hibernate或EclipseLink;
使用JPA与Hibernate类似,但注入的核心资源是带有@PersistenceContext注解的EntityManager代理类

用法基本和hibernate类似
1. 加入依赖 和 hiberate一样
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
2. 使用Hibernate时,我们需要创建一个LocalSessionFactoryBean,并让它再自动创建一个SessionFactory。
   使用JPA也是类似的,我们需要创建一个LocalContainerEntityManagerFactoryBean,
   并让它再自动创建一个EntityManagerFactory:

@Bean
LocalContainerEntityManagerFactoryBean createEntityManagerFactory(@Autowired DataSource dataSource) {
    var entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
    // 设置DataSource:
    entityManagerFactoryBean.setDataSource(dataSource);
    // 扫描指定的package获取所有entity class:
    entityManagerFactoryBean.setPackagesToScan("com.itranswarp.learnjava.entity");
    // 指定JPA的提供商是Hibernate:
    JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);
    // 设定特定提供商自己的配置:
    var props = new Properties();
    props.setProperty("hibernate.hbm2ddl.auto", "update");
    props.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
    props.setProperty("hibernate.show_sql", "true");
    entityManagerFactoryBean.setJpaProperties(props);

    return entityManagerFactoryBean;
}
3. 还需要实例化一个JpaTransactionManager,以实现声明式事务:
   
   @Bean
   PlatformTransactionManager createTxManager(@Autowired EntityManagerFactory entityManagerFactory) {
       return new JpaTransactionManager(entityManagerFactory);
   }

4. 增删改查
注入
我们需要注入一个EntityManager,但是不要使用Autowired,而是@PersistenceContext:

@Component
@Transactional
public class UserService {
    @PersistenceContext
    EntityManager em;  这其实是一个代理
}
Spring遇到标注了@PersistenceContext的EntityManager会自动注入代理,该代理会在必要的时候自动打开EntityManager。
换句话说,多线程引用的EntityManager虽然是同一个代理类,但该代理类内部针对不同线程会创建不同的EntityManager实例。
简单总结一下,标注了@PersistenceContext的EntityManager可以被多线程安全地共享。

get
public User getUserById(long id) {
    User user = this.em.find(User.class, id);
    if (user == null) {
        throw new RuntimeException("User not found by id: " + id);
    }
    return user;
}
Criteria
 // CriteriaBuilder:
 var cb = em.getCriteriaBuilder();
 CriteriaQuery<User> q = cb.createQuery(User.class);
 Root<User> r = q.from(User.class);
 q.where(cb.equal(r.get("email"), cb.parameter(String.class, "e")));
 TypedQuery<User> query = em.createQuery(q);
 // 绑定参数:
 query.setParameter("e", email);
 // 执行查询:
 List<User> list = query.getResultList();
 return list.isEmpty() ? null : list.get(0);

JPQL
TypedQuery<User> query = em.createQuery("SELECT u FROM User u WHERE u.email = :e", 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
@NamedQueries(
    @NamedQuery(
        name = "login",
        query = "SELECT u FROM User u WHERE u.email=:e AND u.password=:p"
    )
)
TypedQuery<User> query = em.createNamedQuery("login", User.class);
query.setParameter("e", email);
query.setParameter("p", password);
List<User> list = query.getResultList();
return list.isEmpty() ? null : list.get(0);

Mybatis

JPA 或者 Hiberate的作用是什么?
就是把结果的ResultSet的每一行变成java bean
或者把Java Bean自动转换到INSERT或UPDATE语句的参数中,从而实现ORM。

这是一个全自动的orm  没茨获取和设置都是动态创建的代理对象进行的判断.
但是这样是有缺点的.

全自动的orm的缺点:

1. 为了实现这样的查询,UserProxy必须保存Hibernate的当前Session。
但是,当事务提交后,Session自动关闭,此时再获取getAddress()将无法访问数据库,
或者获取的不是事务一致的数据。因此,
ORM框架总是引入了Attached/Detached状态,
表示当前此Java Bean到底是在Session的范围内,
还是脱离了Session变成了一个“游离”对象。
很多初学者无法正确理解状态变化和事务边界,就会造成大量的PersistentObjectException异常。
这种隐式状态使得普通Java Bean的生命周期变得复杂。

2. 
此外,Hibernate和JPA为了实现兼容多种数据库,
它使用HQL或JPQL查询,
经过一道转换,变成特定数据库的SQL,
理论上这样可以做到无缝切换数据库,
但这一层自动转换除了少许的性能开销外,
给SQL级别的优化带来了麻烦

3.
ORM框架通常提供了缓存,并且还分为一级缓存和二级缓存。
一级缓存是指在一个Session范围内的缓存,常见的情景是根据主键查询时,两次查询可以返回同一实例:

User user1 = session.load(User.class, 123);
User user2 = session.load(User.class, 123);
二级缓存是指跨Session的缓存,一般默认关闭,需要手动配置。
二级缓存极大的增加了数据的不一致性,原因在于SQL非常灵活,常常会导致意外的更新

对比Spring提供的JdbcTemplate,它和ORM框架相比,主要有几点差别:

查询后需要手动提供Mapper实例以便把ResultSet的每一行变为Java对象;
增删改操作所需的参数列表,需要手动传入,即把User实例变为[user.id, user.name, user.email]这样的列表,比较麻烦。
但是JdbcTemplate的优势在于它的确定性:即每次读取操作一定是数据库操作而不是缓存,
所执行的SQL是完全确定的,缺点就是代码比较繁琐,构造INSERT INTO users VALUES (?,?,?)更是复杂。

为了取其精华去其糟粕? 于是诞生了半自动化的数据库框架mybatis
1. 使用
spring官方没有集成 需要自己集成
引入
org.mybatis:mybatis:3.5.4
org.mybatis:mybatis-spring:2.0.4

2. 创建datadruid
    @Bean
    DataSource createDataSource() { ... }
3. 
可见,ORM的设计套路都是类似的。使用MyBatis的核心就是创建SqlSessionFactory,这里我们需要创建的是SqlSessionFactoryBean:

@Bean
SqlSessionFactoryBean createSqlSessionFactoryBean(@Autowired DataSource dataSource) {
    var sqlSessionFactoryBean = new SqlSessionFactoryBean();
    sqlSessionFactoryBean.setDataSource(dataSource);
    return sqlSessionFactoryBean;
}
4. 
@Bean
PlatformTransactionManager createTxManager(@Autowired DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}
和Hibernate不同的是,MyBatis使用Mapper来实现映射,而且Mapper必须是接口。

public interface UserMapper {
	@Select("SELECT * FROM users WHERE id = #{id}")
	User getById(@Param("id") long id);
}
注意:这里的Mapper不是JdbcTemplate的RowMapper的概念,
它是定义访问users表的接口方法。比如我们定义了一个User getById(long)的主键查询方法,
不仅要定义接口方法本身,还要明确写出查询的SQL,
这里用注解@Select标记。SQL语句的任何参数
,都与方法参数按名称对应。
MyBatis是一个半自动化的ORM框架,需要手写SQL语句,没有自动加载一对多或多对一关系的功能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰明子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值