使用自定义注解实现对实体类指定属性的查询操作DEMO

本文介绍如何使用自定义注解简化实体类属性与数据库列名映射,通过反射和JDBC实现从数据库查询结果并自动填充实体类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一直没什么机会使用自定义注解,今天现学现卖尝试写了个小demo,定义了一个属性注解用于标注在实体类的属性上,一个处理类用于获取从指定数据库查询出对应的结果集。主要是想实现自定义注解的功能思路所以代码中基本没考虑非注解相关各种情况的处理。

注解类MyField.java

package com.test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)  //注解用于属性
@Retention(RetentionPolicy.RUNTIME)  //注解在运行时生效
public @interface MyField {
    //当实体类的属性名于表中的列名不同时,接收列名作为参数
    String columnName() default "";
}

执行查询操作的工具类QueryUtil.java

package com.test;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class QueryUtil {
    /**
     * 从数据库中查询指定类的所有结果
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> List<T> selectAll(Class<T> clazz) {
        //拼接sql
        StringBuilder sql = new StringBuilder("select ");
        //使用反射获取指定类的所有属性
        Field[] fields = clazz.getDeclaredFields();
        //定义一个属性和列名关系的map,一会儿遍历结果集的时候要用
        Map<Field, String> fieldColumnMap = new HashMap<Field, String>();
        //遍历所有属性
        for (Field field : fields) {
            //判断属性是否被我们指定的MyField注解所标识
            if (field.isAnnotationPresent(MyField.class)) {
                //取得当前属性上对应的注解对象
                MyField myField = field.getAnnotation(MyField.class);
                //如果注解指定了列名则用指定的列名,否则直接用属性名
                String columnName = "";
                if (!"".equals(myField.columnName())) {
                    columnName = myField.columnName();
                } else {
                    columnName = field.getName();
                }
                sql.append(columnName + ", ");
                //将有注解标注的属性和对应的列名放进他们对应关系的map
                fieldColumnMap.put(field, columnName);
            }
        }
        //继续拼接sql
        sql.replace(sql.lastIndexOf(","), sql.length() - 1, " from ");
        sql.append(clazz.getSimpleName().toLowerCase());
        //基础的jdbc操作
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        List<T> list = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
            ps = conn.prepareStatement(sql.toString());
            rs = ps.executeQuery();
            list = new ArrayList<T>();
            //遍历结果集
            while (rs.next()) {
                T t = clazz.newInstance();
                //遍历刚才定义的map,通过列名取出结果,通过反射获取对应属性的set方法,组装实体类对象
                for (Map.Entry<Field, String> entry : fieldColumnMap.entrySet()) {
                    Object columnResult = rs.getObject(entry.getValue());
                    Field field = entry.getKey();
                    String fieldName = field.getName();
                    String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
                    clazz.getDeclaredMethod(methodName, field.getType()).invoke(t, columnResult);
                }
                //组装完成,加入方法返回的list
                list.add(t);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (ps != null) {
                    ps.close();
                }
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return list;
    }

}

待查询的对象Book.java

package com.test;

/**
 * 在实体类中,给id设置了默认的@MyField注解,给那nameaaa设置了带参的注解用于注明属性名于列名不同,而author没设注解
 */
public class Book {
    @MyField
    private Integer id;
    @MyField(columnName = "name")
    private String nameaaa;

    private String author;

    public Book() {
    }

    public Integer getId() {

        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }


    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", nameaaa='" + nameaaa + '\'' +
                ", author='" + author + '\'' +
                '}';
    }

    public String getNameaaa() {
        return nameaaa;
    }

    public void setNameaaa(String nameaaa) {
        this.nameaaa = nameaaa;
    }
}

测试类Test.java

package com.test;

import java.util.List;

public class Test {
    public static void main(String[] args) throws Exception {
        List<Book> books = QueryUtil.selectAll(Book.class);
        System.out.println(books);
    }
}

执行结果:

[Book{id=1, nameaaa='红楼梦1', author='null'}, Book{id=2, nameaaa='红楼梦2', author='null'}, Book{id=3, nameaaa='红楼梦3', author='null'}, Book{id=4, nameaaa='红楼梦4', author='null'}, Book{id=5, nameaaa='红楼梦', author='null'}, Book{id=7, nameaaa='红楼梦', author='null'}, Book{id=8, nameaaa='红楼梦', author='null'}, Book{id=9, nameaaa='红楼梦', author='null'}, Book{id=10, nameaaa='红楼梦', author='null'}, Book{id=11, nameaaa='红楼梦', author='null'}, Book{id=12, nameaaa='红楼梦', author='null'}, Book{id=15, nameaaa='红楼梦', author='null'}, Book{id=16, nameaaa='红楼梦', author='null'}, Book{id=17, nameaaa='红楼梦', author='null'}, Book{id=18, nameaaa='红楼梦', author='null'}, Book{id=19, nameaaa='红楼梦', author='null'}, Book{id=20, nameaaa='红楼梦', author='null'}, Book{id=21, nameaaa='红楼梦', author='null'}, Book{id=22, nameaaa='红楼梦', author='null'}, Book{id=23, nameaaa='红楼梦', author='null'}, Book{id=24, nameaaa='红楼梦', author='null'}, Book{id=25, nameaaa='红楼梦', author='null'}, Book{id=26, nameaaa='红楼梦', author='null'}]

Process finished with exit code 0

 

可以看到,在实体类中我们给id设置了默认的@MyField注解,给nameaaa设置了带参的注解用于注明属性名于列名不同,而author没设注解,所以id和name都查出了结果而author虽然在数据库中有值但返回null,查询结果符合预期。

按照这个思路,如果想要实现增删改查只需要在工具类中新增对应的方法,接收对应的参数并完善各种可能发生的情况的处理措施就行了。

<think>我们正在讨论如何在Java使用拦截器(Interceptor)对特定插入操作中的实体类进行赋值。 根据用户的问题,我们需要在实体类被插入到数据库之前,通过拦截器给该实体类的某些属性赋值。 在JPA(Java Persistence API)中,我们可以使用监听器(Listener)来实现类似拦截器的功能,例如使用`@PrePersist`注解。 但是,用户特别提到了“拦截器”(Interceptor),可能是指Hibernate的Interceptor,因为Hibernate提供了Interceptor接口,允许在持久化操作前后执行自定义逻辑。 因此,我们可以考虑两种方式: 1. 使用JPA的生命周期回调(例如`@PrePersist`)在实体被持久化之前自动设置属性。 2. 使用Hibernate的Interceptor,通过重`onSave`方法,在保存实体时修改实体的状态。 由于用户明确提到了“拦截器”,我们将重点放在Hibernate Interceptor上。 步骤: 1. 创建一个自定义的拦截器类,实现Hibernate的`Interceptor`接口(或继承`EmptyInterceptor`类)。 2. 重`onSave`方法,在该方法中判断实体类型,如果是目标实体,则给特定属性赋值。 3. 配置Hibernate使用这个拦截器。 但是,需要注意的是,用户可能是在特定的插入操作使用,所以我们需要能够识别特定的插入操作。我们可以通过拦截器中的上下文信息(如实体名称、属性等)来判断。 另外,用户提到“特定插入操作”,可能是指某些特定的业务场景下的插入,我们可以通过设置拦截器并检查实体的类型和状态来决定是否赋值。 示例代码: 自定义拦截器: ```java import org.hibernate.EmptyInterceptor; import org.hibernate.type.Type; import java.io.Serializable; import java.util.Arrays; public class CustomInterceptor extends EmptyInterceptor { @Override public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { // 检查实体类型,如果是我们关心的实体(例如Book),则进行赋值 if (entity instanceof Book) { // 假设我们要在插入时设置一个属性,例如createTime // 找到属性名对应的索引 int indexOfProperty = Arrays.asList(propertyNames).indexOf("createTime"); if (indexOfProperty != -1) { // 设置当前时间为创建时间 state[indexOfProperty] = new java.util.Date(); return true; // 表示状态被修改 } } return false; // 状态未被修改 } } ``` 配置拦截器: 在Hibernate的配置中,我们可以设置这个拦截器。如果使用Spring Boot,可以在配置文件中指定,或者通过`LocalSessionFactoryBean`设置。 如果是纯Hibernate配置: ```java Configuration configuration = new Configuration(); configuration.setInterceptor(new CustomInterceptor()); ``` 如果是Spring Boot JPA,我们可以通过`EntityManagerFactory`来设置拦截器。但是,Spring Boot JPA默认使用Hibernate作为JPA实现,我们可以通过自定义`EntityManagerFactory`来设置拦截器。 示例(Spring Boot): ```java @Configuration public class HibernateConfig { @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource); em.setPackagesToScan("com.example.demo"); HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); em.setJpaVendorAdapter(vendorAdapter); // 设置Hibernate属性 Properties properties = new Properties(); properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect"); // 其他属性... em.setJpaProperties(properties); // 设置拦截器 em.setJpaPropertyMap(Collections.singletonMap("hibernate.session_factory.interceptor", new CustomInterceptor())); return em; } } ``` 但是,请注意,上面的设置方式在Spring Boot中可能不直接支持,因为`hibernate.session_factory.interceptor`属性期望的是一个拦截器实例,而我们在Spring中可以将拦截器作为Bean管理。 另一种方式是使用`HibernatePropertiesCustomizer`: ```java @Bean public HibernatePropertiesCustomizer hibernatePropertiesCustomizer() { return hibernateProperties -> hibernateProperties.put("hibernate.session_factory.interceptor", new CustomInterceptor()); } ``` 但是,请注意,拦截器是单例的,并且会被所有会话共享。如果拦截器需要状态,那么需要小心处理并发问题。通常,拦截器应该是无状态的。 另外,如果用户只需要在特定插入操作中赋值,我们可能需要在拦截器中获取当前上下文(比如通过ThreadLocal传递一些信息),但这会增加复杂性。 替代方案:使用JPA的`@PrePersist`注解 如果用户的需求相对简单,只是希望在实体插入前设置某些属性(如创建时间、更新时间等),那么使用JPA的生命周期回调更简单。 例如,在实体类中: ```java @Entity public class Book { // 其他字段... private Date createTime; @PrePersist public void prePersist() { this.createTime = new Date(); } } ``` 这种方式更简单,并且不需要配置拦截器。但是,它只能作用于当前实体类,而拦截器可以作用于多个实体类。 用户需要根据具体需求选择。 总结: 1. 如果需要在多个实体类中共享相同的拦截逻辑,或者需要更灵活的控制,使用Hibernate拦截器。 2. 如果只是单个实体类的需求,使用`@PrePersist`更简单。 根据用户的问题,他可能希望有一个全局的拦截器来处理特定插入操作,所以我们提供了拦截器的实现方式。 注意:拦截器可能会影响性能,因为每个实体操作都会经过拦截器,所以应确保拦截器的逻辑尽可能高效。 相关问题: 1. 如何在Spring Boot中配置Hibernate拦截器? 2. Hibernate拦截器还有哪些常用的方法? 3. 除了拦截器和`@PrePersist`,还有哪些方式可以在插入前修改实体? 4. 如何确保拦截器中的线程安全? 引用说明: 在回答中,我们参考了Hibernate拦截器的基本用法和Spring Boot的配置方式。这些是Java持久化领域的常见实践。 由于用户之前的问题中引用了EntityManager的使用,我们假设他正在使用JPA或Hibernate。因此,我们的回答基于这个假设。 引用[1]中展示了使用JPA进行实体插入的代码,我们的拦截器将在这个插入操作之前被调用。 我们并没有直接引用[2],因为它与Activiti相关,与当前问题无关。 因此,我们生成以下相关问题:</think>### 在 Java使用拦截器为特定插入操作中的实体类赋值 在 Java 中,可以通过 **Hibernate 拦截器** 或 **CDI 拦截器** 实现在特定插入操作前为实体类自动赋值。以下是两种实现方式: --- #### 方法 1:使用 Hibernate 拦截器(推荐) **适用场景**:基于 Hibernate/JPA 的持久层操作 **步骤**: 1. **创建自定义拦截器** 继承 `EmptyInterceptor` 并重 `onSave` 方法: ```java public class EntityInterceptor extends EmptyInterceptor { @Override public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { // 仅针对特定插入操作(如 Book 实体) if (entity instanceof Book) { // 定位属性索引 int statusIndex = Arrays.asList(propertyNames).indexOf("status"); if (statusIndex != -1) { // 赋值逻辑(例如设置默认状态) state[statusIndex] = "ACTIVE"; // 修改持久化状态 return true; // 表示实体状态已修改 } } return false; } } ``` 2. **注册拦截器** 在 `persistence.xml` 中配置: ```xml <property name="hibernate.session_factory.interceptor" value="com.example.EntityInterceptor"/> ``` 或在 Spring Boot 中通过 `EntityManagerFactory` 配置: ```java @Bean public EntityManagerFactory entityManagerFactory(DataSource dataSource) { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource); em.setPackagesToScan("com.example.model"); // 注册拦截器 Map<String, Object> properties = new HashMap<>(); properties.put("hibernate.session_factory.interceptor", new EntityInterceptor()); em.setJpaPropertyMap(properties); return em.getObject(); } ``` --- #### 方法 2:使用 CDI 拦截器(Java EE 环境) **适用场景**:Java EE 应用(如 Jakarta EE) **步骤**: 1. **定义拦截器注解** ```java @InterceptorBinding @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface AssignValue {} ``` 2. **实现拦截器逻辑** ```java @Interceptor @AssignValue public class EntityInterceptor { @AroundInvoke public Object assign(InvocationContext ctx) throws Exception { // 获取目标实体(假设方法参数为实体对象) Object entity = ctx.getParameters()[0]; if (entity instanceof Book) { ((Book) entity).setStatus("ACTIVE"); // 直接赋值 } return ctx.proceed(); // 继续执行原方法 } } ``` 3. **在插入操作上应用注解** ```java @Stateless public class BookService { @PersistenceContext private EntityManager em; @AssignValue // 激活拦截器 public void insertBook(Book book) { em.persist(book); } } ``` --- #### 关键注意事项 1. **作用范围控制** - 在 Hibernate 拦截器中通过 `instanceof` 限定特定实体类(如 `Book`)。 - 在 CDI 拦截器中通过注解 `@AssignValue` 精确控制需要拦截的方法。 2. **赋值逻辑** - 直接修改实体对象属性(CDI 方式)。 - 修改 `state[]` 数组(Hibernate 拦截器方式),该数组包含实体属性的持久化值[^1]。 3. **事务边界** 拦截器在事务提交前(`persist()` 或 `flush()` 时)触发,确保赋值操作在数据插入前完成。 4. **性能影响** 避免在拦截器中执行耗时操作,否则会影响所有持久化操作性能。 --- ### 相关问题 1. 如何区分插入和更新操作并在拦截器中实现不同逻辑? 2. 除了拦截器,还有哪些方式能在 JPA 插入前自动填充实体字段? 3. 在 Spring Boot 中如何全局注册 Hibernate 拦截器? 4. 拦截器中修改实体状态与 `@PrePersist` 回调有何区别? [^1]: 引用自用户提供的 JPA 插入操作示例代码,展示了实体插入的基本流程。 [^2]: 引用自 Activiti 的数据库更新记录,提示需检查数据库兼容性(虽不直接相关,但需注意拦截器可能影响数据一致性)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值