← Назад к вопросам

Какие знаешь способы работы с реляционной базой данных?

1.7 Middle🔥 171 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Способы работы с реляционной БД в Java

Уровни абстракции

В Java есть несколько слоев для работы с БД, от низкого уровня к высокому:

┌─────────────────────────────┐
│   ORM (Hibernate, JPA)      │  Объекты ↔ Таблицы
├─────────────────────────────┤
│  Query Builders (QueryDSL)  │  Типобезопасные запросы
├─────────────────────────────┤
│  JDBC / Spring JDBC         │  Прямые SQL запросы
├─────────────────────────────┤
│   Connection Pool (HikariCP)│  Управление соединениями
├─────────────────────────────┤
│   Database (PostgreSQL...)  │
└─────────────────────────────┘

Способ 1: JDBC (Java Database Connectivity)

JDBC — это базовый API для работы с БД, требует ручного управления SQL и результатами.

Использование DriverManager (плохо)

public class JdbcBasicExample {
    
    public List<User> getUsers() throws SQLException {
        // Проблемы: нет пула, медленно, утечки соединений
        Connection conn = DriverManager.getConnection(
            "jdbc:postgresql://localhost:5432/mydb",
            "user",
            "password"
        );
        
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("SELECT * FROM users");
        
        List<User> users = new ArrayList<>();
        while (rs.next()) {
            users.add(new User(
                rs.getInt("id"),
                rs.getString("name")
            ));
        }
        
        rs.close();
        stmt.close();
        conn.close();  // ОБЯЗАТЕЛЬНО! Иначе утечка
        
        return users;
    }
}

С PreparedStatement (лучше)

public class JdbcPreparedExample {
    
    private DataSource dataSource;
    
    public User getUserById(Long id) throws SQLException {
        Connection conn = dataSource.getConnection();
        
        try {
            String sql = "SELECT * FROM users WHERE id = ?";  // Параметр ?
            PreparedStatement stmt = conn.prepareStatement(sql);
            stmt.setLong(1, id);  // Безопасно от SQL injection
            
            ResultSet rs = stmt.executeQuery();
            
            if (rs.next()) {
                return new User(
                    rs.getInt("id"),
                    rs.getString("name"),
                    rs.getString("email")
                );
            }
        } finally {
            conn.close();  // Try-with-resources автоматически
        }
        
        return null;
    }
}

Try-with-resources (правильный способ)

public List<User> getAllUsers() throws SQLException {
    String sql = "SELECT * FROM users ORDER BY id";
    
    try (Connection conn = dataSource.getConnection();
         Statement stmt = conn.createStatement();
         ResultSet rs = stmt.executeQuery(sql)) {
        
        List<User> users = new ArrayList<>();
        while (rs.next()) {
            users.add(mapResultSetToUser(rs));
        }
        
        return users;  // Все ресурсы закроются автоматически
    }
}

Способ 2: Spring JDBC

Spring JDBC упрощает JDBC, убирает boilerplate, но требует писать SQL.

JdbcTemplate

@Repository
public class UserRepository {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    // Получение одного объекта
    public User getUserById(Long id) {
        String sql = "SELECT * FROM users WHERE id = ?";
        
        return jdbcTemplate.queryForObject(
            sql,
            new Object[]{id},
            new UserRowMapper()  // Маппер для преобразования
        );
    }
    
    // Получение списка
    public List<User> getAllUsers() {
        String sql = "SELECT * FROM users ORDER BY id";
        
        return jdbcTemplate.query(
            sql,
            new UserRowMapper()
        );
    }
    
    // Вставка
    public void addUser(User user) {
        String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
        
        jdbcTemplate.update(
            sql,
            user.getName(),
            user.getEmail()
        );
    }
    
    // Обновление
    public void updateUser(User user) {
        String sql = "UPDATE users SET name = ?, email = ? WHERE id = ?";
        
        jdbcTemplate.update(
            sql,
            user.getName(),
            user.getEmail(),
            user.getId()
        );
    }
    
    // Удаление
    public void deleteUser(Long id) {
        String sql = "DELETE FROM users WHERE id = ?";
        jdbcTemplate.update(sql, id);
    }
}

// Маппер для преобразования ResultSet → объект
public class UserRowMapper implements RowMapper<User> {
    
    @Override
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        return new User(
            rs.getLong("id"),
            rs.getString("name"),
            rs.getString("email")
        );
    }
}

Способ 3: JPA / Hibernate ORM

ORM (Object-Relational Mapping) автоматически преобразует объекты в таблицы и наоборот.

Entity определение

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "name", nullable = false, length = 100)
    private String name;
    
    @Column(unique = true)
    private String email;
    
    @CreationTimestamp
    private LocalDateTime createdAt;
    
    // Связь один-ко-многим
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private Set<Order> orders;
    
    // Связь много-к-одному
    @ManyToOne
    @JoinColumn(name = "role_id")
    private Role role;
}

@Entity
@Table(name = "orders")
public class Order {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User user;
    
    private BigDecimal amount;
}

Repository (Spring Data JPA)

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    // Простые запросы — генерируются автоматически
    Optional<User> findByEmail(String email);
    List<User> findByName(String name);
    List<User> findByNameContainingIgnoreCase(String keyword);
    
    // Сложные запросы — пишешь JPQL
    @Query("SELECT u FROM User u WHERE u.email = :email AND u.active = true")
    Optional<User> findActiveUserByEmail(@Param("email") String email);
    
    // Native SQL если нужно
    @Query(value = "SELECT * FROM users WHERE LOWER(name) LIKE %?1%", 
           nativeQuery = true)
    List<User> searchByNameNative(String keyword);
    
    // Модификация
    @Modifying
    @Transactional
    @Query("UPDATE User u SET u.active = false WHERE u.lastLogin < :date")
    int deactivateInactiveUsers(@Param("date") LocalDateTime date);
}

// Использование
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public User getUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException(id));
    }
    
    public List<User> searchUsers(String keyword) {
        return userRepository.findByNameContainingIgnoreCase(keyword);
    }
    
    @Transactional
    public void saveUser(User user) {
        userRepository.save(user);  // INSERT или UPDATE автоматически
    }
}

Способ 4: QueryDSL (Type-Safe Queries)

QueryDSL позволяет писать SQL-подобные запросы с проверкой типов.

// Конфигурация
@Configuration
public class QueryDslConfig {
    
    @Bean
    public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) {
        return new JPAQueryFactory(entityManager);
    }
}

// Repository
@Repository
public class UserQueryDslRepository {
    
    @Autowired
    private JPAQueryFactory queryFactory;
    
    public List<User> findActiveUsers() {
        QUser user = QUser.user;
        
        return queryFactory
            .selectFrom(user)
            .where(user.active.isTrue())
            .orderBy(user.createdAt.desc())
            .fetch();
    }
    
    public Page<User> searchUsers(String keyword, Pageable pageable) {
        QUser user = QUser.user;
        
        List<User> content = queryFactory
            .selectFrom(user)
            .where(user.name.containsIgnoreCase(keyword))
            .orderBy(user.createdAt.desc())
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .fetch();
        
        long total = queryFactory
            .selectFrom(user)
            .where(user.name.containsIgnoreCase(keyword))
            .fetchCount();
        
        return new PageImpl<>(content, pageable, total);
    }
}

Способ 5: Mybatis

Mybatis находится между Spring JDBC и ORM — удобен, но требует писать XML конфиги.

<!-- mapper.xml -->
<mapper namespace="com.example.UserMapper">
    <select id="getUserById" resultType="User">
        SELECT id, name, email FROM users WHERE id = #{id}
    </select>
    
    <insert id="addUser" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO users (name, email) VALUES (#{name}, #{email})
    </insert>
    
    <update id="updateUser">
        UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}
    </update>
    
    <delete id="deleteUser">
        DELETE FROM users WHERE id = #{id}
    </delete>
</mapper>
@Mapper
public interface UserMapper {
    User getUserById(Long id);
    void addUser(User user);
    void updateUser(User user);
    void deleteUser(Long id);
}

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
}

Сравнение подходов

ПодходСкорость разработкиКонтрольПроизводительностьЛучше для
JDBCМедленнаяПолныйОтличнаяСпецифичные запросы
Spring JDBCСредняяХорошийХорошаяSQL-first подход
JPA/HibernateБыстраяМеньшеЗависитСтандартные CRUD
QueryDSLБыстраяХорошийХорошаяСложные условия
MybatisСредняяПолныйОтличнаяБалант SQL + объекты

Best Practices

  1. Используй JPA для стандартных приложений:

    Spring Data JPA + Hibernate = минимум кода, максимум работы
    
  2. QueryDSL для сложных фильтров:

    Type-safe, readable, efficient
    
  3. Native SQL только когда нужна оптимизация:

    @Query(value = "...", nativeQuery = true)
    
  4. Всегда используй Connection Pool:

    HikariCP в Spring Boot по умолчанию
    
  5. Правильно управляй транзакциями:

    @Transactional
    public void updateUser(User user) { ... }
    

Вывод

Для работы с реляционной БД в Java есть 5 основных подходов:

  1. JDBC — полный контроль, но много boilerplate
  2. Spring JDBC — баланс, когда нужен контроль над SQL
  3. JPA/Hibernate — быстро разрабатывать стандартные CRUD
  4. QueryDSL — type-safe запросы с хорошей читаемостью
  5. Mybatis — когда хочешь SQL + удобство объектов

Рекомендация: для большинства случаев используй JPA/Hibernate с Spring Data JPA.