Mybatis和JPA注解开发

1、Mybatis


上文写过,使用Mybatis逆向工程可以生成很多常用的方法,但是如果我们想自己手写sql的话,又不想写到xml文件中,或者只是单纯的想手写sql,不使用mybatis的xml文件格式,那么我们就可以通过注解的形式来达到需求。注:mybatis注解和下面讲到的JPA的最大区别是,mybatis需要手动建立好数据库表,但是JPA不需要。具体的内容如下:

首先,我们需要在pom.xml文件中加入mybatis和数据库的依赖

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!-- 数据库驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>

其次,我们需要配置好数据库的连接,保证可以正常访问到数据库,在application.properties中配置

## 配置数据源参数
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shirodemo?serverTimezone=Hongkong
spring.datasource.username=root
spring.datasource.password=*******
## 显示mybatis执行的sql语句,语句下面会按顺序出现每个参数的值
logging.level.cn.henu.dao=debug

最后我们看一下代码结构

1:pojo(这里使用了lombok,后面使用jpa的时候建议自己手写getset以及tostring,否则可能导致循环依赖)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private int id;
    private String username;
    private String password;
    private int age;
}

2:dao层:数据库操作层

@Repository
public interface UserDao {
    //插入的内容要与前面写的顺序和字段对应,如果是全部字段都插入,则可以省略values前面的字段
    @Insert("insert into user(id,password,age) values (#{id}, #{password}, #{age})")
    public int addUserM(User user);

    //根据user中的id属性查询 ,这里id是唯一的,显然查询一条就够了,不用全表扫描
    @Delete("delete  from user where id=#{id} limit 1")
    public int delUserM(User user);

    //如果不加后面的where则表示对全表进行操作
    @Update("update user  set username=#{username} ,password=#{password},age=#{age} where id =#{id} ")
    public int editUserM(User user);

    @Select("select * from user where username=#{username}")
    public List<User> seaUserM(User user);
}

注意:如果是方法中的参数名和查询语句中的名称不一致,可以在方法中的参数列表中添加@Param注解。

3:Service层:(这里省略了接口层,直接写实现)

@Service
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    @Override
    public int addUserM(User user) {
        return userDao.addUserM(user);
    }

    @Override
    public int delUserM(User user) {
        return userDao.delUserM(user);
    }

    @Override
    public int editUserM(User user) {
        return userDao.editUserM(user);
    }

    @Override
    public List<User> seaUserM(User user) {
        return userDao.seaUserM(user);
    }
    
}

4:最后我们再看一下controller层


@Controller
@Transactional
public class UserController {
    @Autowired
    private UserService userService;

    //mybatis添加数据
    //@Transactional  //在方法上加事务注解,只对该方法有效
    @RequestMapping("/addUserM")
    @ResponseBody
    public String addUserByMybatis(){
        User user = new User();
        //由于建立表格的时候id采用了自动增长的策略,所以这里可以不指定id,后台插入的时候也是会自动插入的。
        user.setAge(23);
        user.setUsername("syw");
        user.setPassword("lmy");
        int i = userService.addUserM(user);
        if(i>0){
            int exe=i/0;
            return i+"";
        }
        return i+"";
    }

    //mybatis删除数据
    @RequestMapping("/del/{id}")
    @ResponseBody
    public String delUserByMybatis(@PathVariable("id") int id){
        User user = new User();
        user.setId(id);
        if(userService.delUserM(user)>0){
            int j=10/0;
            return "删除成功";
        }else{
            return "删除失败";
        }
    }
    //mybatis修改数据
    @RequestMapping("/edit/{id}")
    @ResponseBody
    public String editUserByMybatis(@PathVariable("id") int id){
        User user = new User();
        //这里修改对应id的数据的年龄为10
        user.setAge(10);
        user.setId(id);
        return userService.editUserM(user)+"";
    }

    //mybatis查询数据
    @RequestMapping("/sea/{username}")
    @ResponseBody
    public List<User> seaUserByName(@PathVariable("username") String username){
        User user = new User();
        user.setUsername(username);
        return userService.seaUserM(user);
    }
}

到此,使用mybatis注解开发就结束了,该方式只针对简单的逻辑,复杂的还是要写xml中。

2、JPA


Java 持久层框架访问数据库的方式大致分为两种。
    1:一种以 SQL 核心,封装一定程度的 JDBC 操作,比如: MyBatis。
    2: 另一种是以 Java 实体类为核心,将实体类的和数据库表之间建立映射关系,也就是我们说的ORM框架,如:Hibernate、Spring Data JPA

1.JPA是什么
JPA全称为Java Persistence APIJava持久层API),是一个基于ORM(或叫O/R mapping ,对象关系映射) 的标准规范,在这个规范中,JPA只定义标准规则,不提供实现。
    它是Sun公司在Java5中提出的Java持久化规范,内部由一系列的接口和抽象类构成。 可以通过注解或者XML描述对象-关系表之间的映射关系,并将运行期的实体对象持久化到数据库。
    目前,JPA的主要实现有Hibernate,EclipseLink,OpenJPA等,由于Hibernate在数据访问解决技术领域的霸主地位,所以JPA标准基本由Hibernate主导。
    需要注意的是JPA统一了Java应用程序访问ORM框架的规范,使得应用程序以统一的方式访问持久层我们知道不同的数据库厂商都有自己的实现类,后来统一规范也就有了数据库驱动,Java在操作数据库的时候,底层使用的其实是JDBC,而JDBC是一组操作不同数据库的规范。我们的Java应用程序,只需要调用JDBC提供的API就可以访问数据库了,而JPA也是类似的道理。

2.JPA提供的规范
JPA为我们提供了以下规范:
    1:ORM映射元数据:JPA支持XML和注解两种元数据的形式描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中
        如:@Entity、@Table、@Column、@Transient等注解。
   2: JPA 的API:用来操作实体对象,执行CRUD操作,框架在后台替我们完成所有的事情,开发者从繁琐的JDBC和SQL代码中解脱出来
        如:entityManager.merge(T t);
   3: JPQL查询语言:通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合。
        JPA定义了独特的JPQL(Java Persistence Query Language),JPQL是EJBQL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
        如:from Student s where s.name = ?
但是:JPA仅仅是一种规范,也就是说JPA仅仅定义了一些接口,而接口是需要实现才能工作的。所以底层需要某种实现,而Hibernate就是实现了JPA接口的ORM框架。
Hibernate 和 JPA的关系
3.JPA和Hibernate的关系
    JPA和Hibernate的关系就像JDBC和JDBC驱动的关系,JPA是规范,Hibernate除了作为ORM框架之外,它也是一种JPA实现。JPA怎么取代Hibernate呢?JDBC规范可以驱动底层数据库吗?答案是否定的,也就是说,如果使用JPA规范进行数据库操作,底层需要hibernate作为其实现类完成数据持久化工作。
JPA和Hibernate的关系:
    JPA是一个规范,而不是框架
    Hibernate是JPA的一种实现,是一个框架

话不多说,我们直接看一下项目如何使用springbootjpa吧:

1:Pojo类

@Entity(name = "flowever") //该注解可以使用jpa自动创建数据库表格
@Table  //这里可以添加name属性,表示所要创建的数据库中的表的名字
@Data
public class Flowever {
    @Id //主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) //主键自增
    private Integer floweverId;
    //@Column(name = "df") //如果数据库表中属性的名称和这个不一致,则指出
    private String floweverName;
    private Date datatime;
    private String detail;
}

2:Controller层:

@Controller
public class FloweverController {
    @Autowired
    private FloweverService floweverService;
    //添加花朵
    @RequestMapping("/addFlowever")
    @ResponseBody
    public Flowever addFlowever(){
        Flowever flowever = new Flowever();
        flowever.setDatatime(new Date());
        flowever.setDetail("213");
        return floweverService.addFlowJPA(flowever);
    }
    //修改花朵
    @RequestMapping("/editFlowever")
    @ResponseBody
    public String editFlowever(){
        Flowever flowever = new Flowever();
        //注意,如果不设置修改的id则表示重新添加一个新的用户,指定id之后并不是在
        //原有的基础上进行修改,而是用新的数据覆盖老的数据,如果新数据某些字段没有添加
        //即使老数据存在该字段,也不会保留。
        flowever.setFloweverId(2);
        flowever.setDetail("9999");
        flowever.setFloweverName("火红");
        return floweverService.editFlow(flowever.getDetail(),flowever.getFloweverId())+"";
    }
    //删除花朵
    @RequestMapping("/delFlowever")
    @ResponseBody
    public String delFlowever(){
        Flowever flowever = new Flowever();
        flowever.setFloweverId(1); //删除id为1的数据
        floweverService.delFlowJPA(flowever);
        return "ok";
    }
    //查找所有花朵
    @RequestMapping("/findAllFlowever")
    @ResponseBody
    public List<Flowever> findAllFlow(){
        return floweverService.seaFlowJPA();
    }
    //根据id查找花朵
    @RequestMapping("/findByid/{id}")
    @ResponseBody
    public Flowever findFlowById(@PathVariable("id")int id){
        return floweverService.seaFlowById(id);
    }
}

3:Service层

@Service
public class FloweverServiceImpl implements FloweverService {
    @Autowired
    private FloweverDao floweverDao;
   //添加和删除使用的都是save方法
    @Override
    public Flowever addFlowJPA(Flowever flowever) {
        return floweverDao.save(flowever);
    }

    //删除数据,默认的删除方法是没有返回值的
    @Override
    public Flowever delFlowJPA(Flowever flowever) {
        floweverDao.delete(flowever);
        return flowever;
    }

    //修改和添加一样,都使用save方法,默认返回添加的数据
    @Override
    public Flowever editFlowJPA(Flowever flowever) {
        return floweverDao.save(flowever);
    }

    // 查询所有的数据,使用findAll方法
    @Override
    public List<Flowever> seaFlowJPA() {
        return floweverDao.findAll();
    }
    // 修改之后返回int类型
    @Override
    public int editFlow(String detail, int id){
        return floweverDao.editFlow(detail, id);
    }
    //根据id进行查询
    @Override
    public Flowever seaFlowById(int id) {
        return floweverDao.findById(id);
    }
}

4:Dao层

// 这里直接继承JpaRepository就会自动拥有一些写好的方法,其中Flowever表示实体类的名字,Integer表示主键类型
public interface FloweverDao extends JpaRepository<Flowever, Integer> {
    //下面是继承之外手写的方法
    //根据id进行查询,这里的f是flowever的别名,注意 前面的pojo类中的Entity注解中要添加name属性为flowever,否则这里会报错
    @Query(value = "select * from flowever f where f.flowever_id=?1",nativeQuery = true)
    public Flowever findById(int id);

    //修改并且返回int
    @Modifying   //修改和删除需要额外添加此注解
    @Transactional  //修改添加和删除需要添加事务
    @Query(value = "update flowever f set f.detail=?1 where f.flowever_id=?2 ",nativeQuery = true)
    public int editFlow(String detail, Integer id);

}

5:这里与mybatis的不同是,jpa在启动类上需要开启:

@SpringBootApplication
@MapperScan("cn.henu.dao")
@EntityScan(basePackages = {"cn.henu.pojo"})  //JPA实体扫描
@EnableJpaRepositories(basePackages = {"cn.henu.dao"}) //JPA数据操作
public class MybatisJpaApplication {
    public static void main(String[] args) {
        SpringApplication.run(MybatisJpaApplication.class, args);
    }
}

到此这两种基于注解的开发方法就简单的总结完了,感兴趣的朋友可以深入了解。

常见问题:

用Mybatis查询信息,总条数可以查出来,但是查具体内容都是null
原因

因为数据库的字段名和实体类中的属性名不一样

可能数据库中为article_id,但是在实体类中使用的可能就是articleId,也就是有些人可能习惯实体类把数据库中的_和后面的小写字母变成大写字母。(使用逆向工程好像也默认这样生成,但是逆向工程生成的xml文件中有一个实体类到数据库字段的映射,一般在最前面,id为BaseResultMap,这样不会出错了)。

<resultMap id="BaseResultMap" type="cn.henu.pojo.User">
        <id column="user_id" property="userId" jdbcType="INTEGER"/>
        <result column="user_name" property="userName" jdbcType="VARCHAR"/>
        <result column="online_time" property="onlineTime" jdbcType="VARCHAR"/>
        <result column="user_mail" property="userMail" jdbcType="VARCHAR"/>
        <result column="user_address" property="userAddress" jdbcType="VARCHAR"/>
        <result column="user_age" property="userAge" jdbcType="INTEGER"/>
        <result column="user_sex" property="userSex" jdbcType="INTEGER"/>
        <result column="user_qq" property="userQq" jdbcType="INTEGER"/>
        <result column="user_desc" property="userDesc" jdbcType="VARCHAR"/>
        <result column="user_updatetime" property="userUpdatetime" jdbcType="TIMESTAMP"/>
        <result column="user_createtime" property="userCreatetime" jdbcType="TIMESTAMP"/>
        <result column="user_image" property="userImage" jdbcType="VARCHAR"/>
        <result column="user_password" property="userPassword" jdbcType="VARCHAR"/>
    </resultMap>

如上面的例子所示。

全部评论